]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge remote-tracking branch 'origin/master' into samual/weapons
authorSamual Lenks <samual@xonotic.org>
Sat, 14 Sep 2013 04:08:09 +0000 (00:08 -0400)
committerSamual Lenks <samual@xonotic.org>
Sat, 14 Sep 2013 04:08:09 +0000 (00:08 -0400)
Conflicts:
qcsrc/common/weapons/w_fireball.qc
qcsrc/server/w_crylink.qc
qcsrc/server/w_grenadelauncher.qc
qcsrc/server/w_minelayer.qc
qcsrc/server/w_rocketlauncher.qc

1  2 
qcsrc/common/weapons/w_crylink.qc
qcsrc/common/weapons/w_devastator.qc
qcsrc/common/weapons/w_electro.qc
qcsrc/common/weapons/w_fireball.qc
qcsrc/common/weapons/w_minelayer.qc
qcsrc/common/weapons/w_mortar.qc
qcsrc/server/cheats.qc
qcsrc/server/cl_player.qc
qcsrc/server/g_damage.qc
qcsrc/server/mutators/mutator_minstagib.qc
qcsrc/server/weapons/accuracy.qc

index 12320a850261536e502e01f3629566c49cc665b5,0000000000000000000000000000000000000000..4e46f1fb2c1e45756c4bf17b52088eb3f0240588
mode 100644,000000..100644
--- /dev/null
@@@ -1,712 -1,0 +1,712 @@@
-                       if(IsDifferentTeam(head, projectile.realowner))
-                               ++hit_enemy;
-                       else
 +#ifdef REGISTER_WEAPON
 +REGISTER_WEAPON(
 +/* WEP_##id */ CRYLINK,
 +/* function */ w_crylink,
 +/* ammotype */ IT_CELLS,
 +/* impulse  */ 6,
 +/* flags    */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
 +/* rating   */ BOT_PICKUP_RATING_MID,
 +/* model    */ "crylink",
 +/* netname  */ "crylink",
 +/* fullname */ _("Crylink")
 +);
 +
 +#define CRYLINK_SETTINGS(weapon) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, ammo) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, animtime) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, damage) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, edgedamage) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, radius) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, force) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, spread) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, refire) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, speed) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, shots) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, bounces) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, bouncedamagefactor) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, middle_lifetime) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, middle_fadetime) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, other_lifetime) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, other_fadetime) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, linkexplode) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, joindelay) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, joinspread) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, joinexplode) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, joinexplode_damage) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, joinexplode_edgedamage) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, joinexplode_radius) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, joinexplode_force) \
 +      WEP_ADD_CVAR(weapon, MO_SEC,  spreadtype) \
 +      WEP_ADD_PROP(weapon, reloading_ammo, reload_ammo) \
 +      WEP_ADD_PROP(weapon, reloading_time, reload_time) \
 +      WEP_ADD_PROP(weapon, switchdelay_raise, switchdelay_raise) \
 +      WEP_ADD_PROP(weapon, switchdelay_drop, switchdelay_drop)
 +
 +#ifdef SVQC
 +CRYLINK_SETTINGS(crylink)
 +.float gravity;
 +.float crylink_waitrelease;
 +.entity crylink_lastgroup;
 +
 +.entity queuenext;
 +.entity queueprev;
 +#endif
 +#else
 +#ifdef SVQC
 +void spawnfunc_weapon_crylink() { weapon_defaultspawnfunc(WEP_CRYLINK); }
 +
 +void W_Crylink_CheckLinks(entity e)
 +{
 +      float i;
 +      entity p;
 +
 +      if(e == world)
 +              error("W_Crylink_CheckLinks: entity is world");
 +      if(e.classname != "spike" || wasfreed(e))
 +              error(sprintf("W_Crylink_CheckLinks: entity is not a spike but a %s (freed: %d)", e.classname, wasfreed(e)));
 +
 +      p = e;
 +      for(i = 0; i < 1000; ++i)
 +      {
 +              if(p.queuenext.queueprev != p || p.queueprev.queuenext != p)
 +                      error("W_Crylink_CheckLinks: queue is inconsistent");
 +              p = p.queuenext;
 +              if(p == e)
 +                      break;
 +      }
 +      if(i >= 1000)
 +              error("W_Crylink_CheckLinks: infinite chain");
 +}
 +
 +void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next)
 +{
 +      W_Crylink_CheckLinks(next);
 +      if(me == own.crylink_lastgroup)
 +              own.crylink_lastgroup = ((me == next) ? world : next);
 +      prev.queuenext = next;
 +      next.queueprev = prev;
 +      me.classname = "spike_oktoremove";
 +      if(me != next)
 +              W_Crylink_CheckLinks(next);
 +}
 +
 +void W_Crylink_Dequeue(entity e)
 +{
 +      W_Crylink_Dequeue_Raw(e.realowner, e.queueprev, e, e.queuenext);
 +}
 +
 +void W_Crylink_Reset(void)
 +{
 +      W_Crylink_Dequeue(self);
 +      remove(self);
 +}
 +
 +// force projectile to explode
 +void W_Crylink_LinkExplode (entity e, entity e2)
 +{
 +      float a;
 +
 +      if(e == e2)
 +              return;
 +
 +      a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
 +
 +      if(e == e.realowner.crylink_lastgroup)
 +              e.realowner.crylink_lastgroup = world;
 +              
 +      float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
 +              
 +      RadiusDamage(e, e.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * a, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * a, WEP_CVAR_BOTH(crylink, isprimary, radius), world, world, WEP_CVAR_BOTH(crylink, isprimary, force) * a, e.projectiledeathtype, other);
 +
 +      W_Crylink_LinkExplode(e.queuenext, e2);
 +
 +      e.classname = "spike_oktoremove";
 +      remove (e);
 +}
 +
 +// adjust towards center
 +// returns the origin where they will meet... and the time till the meeting is
 +// stored in w_crylink_linkjoin_time.
 +// could possibly network this origin and time, and display a special particle
 +// effect when projectiles meet there :P
 +// jspeed: joining speed (calculate this as join spread * initial speed)
 +float w_crylink_linkjoin_time;
 +vector W_Crylink_LinkJoin(entity e, float jspeed)
 +{
 +      vector avg_origin, avg_velocity;
 +      vector targ_origin;
 +      float avg_dist, n;
 +      entity p;
 +
 +      // FIXME remove this debug code
 +      W_Crylink_CheckLinks(e);
 +
 +      w_crylink_linkjoin_time = 0;
 +
 +      avg_origin = e.origin;
 +      avg_velocity = e.velocity;
 +      n = 1;
 +      for(p = e; (p = p.queuenext) != e; )
 +      {
 +              avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
 +              avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
 +              ++n;
 +      }
 +      avg_origin *= (1.0 / n);
 +      avg_velocity *= (1.0 / n);
 +
 +      if(n < 2)
 +              return avg_origin; // nothing to do
 +
 +      // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
 +      avg_dist = pow(vlen(e.origin - avg_origin), 2);
 +      for(p = e; (p = p.queuenext) != e; )
 +              avg_dist += pow(vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin), 2);
 +      avg_dist *= (1.0 / n);
 +      avg_dist = sqrt(avg_dist);
 +
 +      if(avg_dist == 0)
 +              return avg_origin; // no change needed
 +
 +      if(jspeed == 0)
 +      {
 +              e.velocity = avg_velocity;
 +              UpdateCSQCProjectile(e);
 +              for(p = e; (p = p.queuenext) != e; )
 +              {
 +                      p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity);
 +                      UpdateCSQCProjectile(p);
 +              }
 +              targ_origin = avg_origin + 1000000000 * normalize(avg_velocity); // HUUUUUUGE
 +      }
 +      else
 +      {
 +              w_crylink_linkjoin_time = avg_dist / jspeed;
 +              targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
 +
 +              e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
 +              UpdateCSQCProjectile(e);
 +              for(p = e; (p = p.queuenext) != e; )
 +              {
 +                      p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
 +                      UpdateCSQCProjectile(p);
 +              }
 +
 +              // analysis:
 +              //   jspeed -> +infinity:
 +              //      w_crylink_linkjoin_time -> +0
 +              //      targ_origin -> avg_origin
 +              //      p->velocity -> HUEG towards center
 +              //   jspeed -> 0:
 +              //      w_crylink_linkjoin_time -> +/- infinity
 +              //      targ_origin -> avg_velocity * +/- infinity
 +              //      p->velocity -> avg_velocity
 +              //   jspeed -> -infinity:
 +              //      w_crylink_linkjoin_time -> -0
 +              //      targ_origin -> avg_origin
 +              //      p->velocity -> HUEG away from center
 +      }
 +
 +      W_Crylink_CheckLinks(e);
 +
 +      return targ_origin;
 +}
 +
 +void W_Crylink_LinkJoinEffect_Think()
 +{
 +      // is there at least 2 projectiles very close?
 +      entity e, p;
 +      float n;
 +      e = self.owner.crylink_lastgroup;
 +      n = 0;
 +      if(e)
 +      {
 +              if(vlen(e.origin - self.origin) < vlen(e.velocity) * frametime)
 +                      ++n;
 +              for(p = e; (p = p.queuenext) != e; )
 +              {
 +                      if(vlen(p.origin - self.origin) < vlen(p.velocity) * frametime)
 +                              ++n;
 +              }
 +              if(n >= 2)
 +              {
 +                      float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY);
 +                      
 +                      if(WEP_CVAR_BOTH(crylink, isprimary, joinexplode))
 +                      {
 +                              n /= WEP_CVAR_BOTH(crylink, isprimary, shots);
 +                              RadiusDamage(e, e.realowner, WEP_CVAR_BOTH(crylink, isprimary, joinexplode_damage) * n,
 +                                                                                       WEP_CVAR_BOTH(crylink, isprimary, joinexplode_edgedamage) * n,
 +                                                                                       WEP_CVAR_BOTH(crylink, isprimary, joinexplode_radius) * n, e.realowner, world,
 +                                                                                       WEP_CVAR_BOTH(crylink, isprimary, joinexplode_force) * n, e.projectiledeathtype, other);
 +                              pointparticles(particleeffectnum("crylink_joinexplode"), self.origin, '0 0 0', n);
 +                      }
 +              }
 +      }
 +      remove(self);
 +}
 +
 +float W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad)
 +{
 +      entity head = WarpZone_FindRadius((projectile.origin + (projectile.mins + projectile.maxs) * 0.5), rad + MAX_DAMAGEEXTRARADIUS, FALSE);
 +      float hit_friendly = 0;
 +      float hit_enemy = 0;
 +
 +      while(head)
 +      {
 +              if((head.takedamage != DAMAGE_NO) && (head.deadflag == DEAD_NO))
 +              {
++                      if(SAME_TEAM(head, projectile.realowner))
 +                              ++hit_friendly;
++                      else
++                              ++hit_enemy;
 +              }
 +                      
 +              head = head.chain;
 +      }
 +
 +      return (hit_enemy ? FALSE : hit_friendly);
 +}
 +
 +// NO bounce protection, as bounces are limited!
 +void W_Crylink_Touch (void)
 +{
 +      float finalhit;
 +      float f;
 +      float isprimary = !(self.projectiledeathtype & HITTYPE_SECONDARY);
 +      PROJECTILE_TOUCH;
 +
 +      float a;
 +      a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1);
 +
 +      finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
 +      if(finalhit)
 +              f = 1;
 +      else
 +              f = WEP_CVAR_BOTH(crylink, isprimary, bouncedamagefactor);
 +      if(a)
 +              f *= a;
 +
 +      float totaldamage = RadiusDamage(self, self.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * f, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * f, WEP_CVAR_BOTH(crylink, isprimary, radius), world, world, WEP_CVAR_BOTH(crylink, isprimary, force) * f, self.projectiledeathtype, other);
 +              
 +      if(totaldamage && ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 2) || ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 1) && !W_Crylink_Touch_WouldHitFriendly(self, WEP_CVAR_BOTH(crylink, isprimary, radius)))))
 +      {
 +              if(self == self.realowner.crylink_lastgroup)
 +                      self.realowner.crylink_lastgroup = world;
 +              W_Crylink_LinkExplode(self.queuenext, self);
 +              self.classname = "spike_oktoremove";
 +              remove (self);
 +              return;
 +      }
 +      else if(finalhit)
 +      {
 +              // just unlink
 +              W_Crylink_Dequeue(self);
 +              remove(self);
 +              return;
 +      }
 +      self.cnt = self.cnt - 1;
 +      self.angles = vectoangles(self.velocity);
 +      self.owner = world;
 +      self.projectiledeathtype |= HITTYPE_BOUNCE;
 +      // commented out as it causes a little hitch...
 +      //if(proj.cnt == 0)
 +      //      CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE);
 +}
 +
 +void W_Crylink_Fadethink (void)
 +{
 +      W_Crylink_Dequeue(self);
 +      remove(self);
 +}
 +
 +void W_Crylink_Attack (void)
 +{
 +      float counter, shots;
 +      entity proj, prevproj, firstproj;
 +      vector s;
 +      vector forward, right, up;
 +      float maxdmg;
 +
 +      W_DecreaseAmmo(ammo_cells, autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_reload_ammo);
 +
 +      maxdmg = WEP_CVAR_PRI(crylink, damage) * WEP_CVAR_PRI(crylink, shots);
 +      maxdmg *= 1 + WEP_CVAR_PRI(crylink, bouncedamagefactor) * WEP_CVAR_PRI(crylink, bounces);
 +      if(WEP_CVAR_PRI(crylink, joinexplode))
 +              maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage);
 +
 +      W_SetupShot (self, FALSE, 2, "weapons/crylink_fire.wav", CH_WEAPON_A, maxdmg);
 +      forward = v_forward;
 +      right = v_right;
 +      up = v_up;
 +
 +      shots = WEP_CVAR_PRI(crylink, shots);
 +      pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots);
 +      proj = prevproj = firstproj = world;
 +      for(counter = 0; counter < shots; ++counter)
 +      {
 +              proj = spawn ();
 +              proj.reset = W_Crylink_Reset;
 +              proj.realowner = proj.owner = self;
 +              proj.classname = "spike";
 +              proj.bot_dodge = TRUE;
 +              proj.bot_dodgerating = WEP_CVAR_PRI(crylink, damage);
 +              if(shots == 1) {
 +                      proj.queuenext = proj;
 +                      proj.queueprev = proj;
 +              }
 +              else if(counter == 0) { // first projectile, store in firstproj for now
 +                      firstproj = proj;
 +              }
 +              else if(counter == shots - 1) { // last projectile, link up with first projectile
 +                      prevproj.queuenext = proj;
 +                      firstproj.queueprev = proj;
 +                      proj.queuenext = firstproj;
 +                      proj.queueprev = prevproj;
 +              }
 +              else { // else link up with previous projectile
 +                      prevproj.queuenext = proj;
 +                      proj.queueprev = prevproj;
 +              }
 +
 +              prevproj = proj;
 +
 +              proj.movetype = MOVETYPE_BOUNCEMISSILE;
 +              PROJECTILE_MAKETRIGGER(proj);
 +              proj.projectiledeathtype = WEP_CRYLINK;
 +              //proj.gravity = 0.001;
 +
 +              setorigin (proj, w_shotorg);
 +              setsize(proj, '0 0 0', '0 0 0');
 +
 +
 +              s = '0 0 0';
 +              if (counter == 0)
 +                      s = '0 0 0';
 +              else
 +              {
 +                      makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
 +                      s_y = v_forward_x;
 +                      s_z = v_forward_y;
 +              }
 +              s = s * WEP_CVAR_PRI(crylink, spread) * g_weaponspreadfactor;
 +              W_SetupProjectileVelocityEx(proj, w_shotdir + right * s_y + up * s_z, v_up, WEP_CVAR_PRI(crylink, speed), 0, 0, 0, FALSE);
 +              proj.touch = W_Crylink_Touch;
 +
 +              proj.think = W_Crylink_Fadethink;
 +              if(counter == 0)
 +              {
 +                      proj.fade_time = time + WEP_CVAR_PRI(crylink, middle_lifetime);
 +                      proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, middle_fadetime);
 +                      proj.nextthink = time + WEP_CVAR_PRI(crylink, middle_lifetime) + WEP_CVAR_PRI(crylink, middle_fadetime);
 +              }
 +              else
 +              {
 +                      proj.fade_time = time + WEP_CVAR_PRI(crylink, other_lifetime);
 +                      proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, other_fadetime);
 +                      proj.nextthink = time + WEP_CVAR_PRI(crylink, other_lifetime) + WEP_CVAR_PRI(crylink, other_fadetime);
 +              }
 +              proj.teleport_time = time + WEP_CVAR_PRI(crylink, joindelay);
 +              proj.cnt = WEP_CVAR_PRI(crylink, bounces);
 +              //proj.scale = 1 + 1 * proj.cnt;
 +
 +              proj.angles = vectoangles (proj.velocity);
 +
 +              //proj.glow_size = 20;
 +
 +              proj.flags = FL_PROJECTILE;
 +    proj.missile_flags = MIF_SPLASH;
 +    
 +              CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE);
 +
 +              other = proj; MUTATOR_CALLHOOK(EditProjectile);
 +      }
 +      if(WEP_CVAR_PRI(crylink, joinspread) != 0)
 +      {
 +              self.crylink_lastgroup = proj;
 +              W_Crylink_CheckLinks(proj);
 +              self.crylink_waitrelease = 1;
 +      }
 +}
 +
 +void W_Crylink_Attack2 (void)
 +{
 +      float counter, shots;
 +      entity proj, prevproj, firstproj;
 +      vector s;
 +      vector forward, right, up;
 +      float maxdmg;
 +
 +      W_DecreaseAmmo(ammo_cells, autocvar_g_balance_crylink_secondary_ammo, autocvar_g_balance_crylink_reload_ammo);
 +
 +      maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots);
 +      maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces);
 +      if(WEP_CVAR_SEC(crylink, joinexplode))
 +              maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage);
 +
 +      W_SetupShot (self, FALSE, 2, "weapons/crylink_fire2.wav", CH_WEAPON_A, maxdmg);
 +      forward = v_forward;
 +      right = v_right;
 +      up = v_up;
 +
 +      shots = WEP_CVAR_SEC(crylink, shots);
 +      pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots);
 +      proj = prevproj = firstproj = world;
 +      for(counter = 0; counter < shots; ++counter)
 +      {
 +              proj = spawn ();
 +              proj.reset = W_Crylink_Reset;
 +              proj.realowner = proj.owner = self;
 +              proj.classname = "spike";
 +              proj.bot_dodge = TRUE;
 +              proj.bot_dodgerating = WEP_CVAR_SEC(crylink, damage);
 +              if(shots == 1) {
 +                      proj.queuenext = proj;
 +                      proj.queueprev = proj;
 +              }
 +              else if(counter == 0) { // first projectile, store in firstproj for now
 +                      firstproj = proj;
 +              }
 +              else if(counter == shots - 1) { // last projectile, link up with first projectile
 +                      prevproj.queuenext = proj;
 +                      firstproj.queueprev = proj;
 +                      proj.queuenext = firstproj;
 +                      proj.queueprev = prevproj;
 +              }
 +              else { // else link up with previous projectile
 +                      prevproj.queuenext = proj;
 +                      proj.queueprev = prevproj;
 +              }
 +
 +              prevproj = proj;
 +
 +              proj.movetype = MOVETYPE_BOUNCEMISSILE;
 +              PROJECTILE_MAKETRIGGER(proj);
 +              proj.projectiledeathtype = WEP_CRYLINK | HITTYPE_SECONDARY;
 +              //proj.gravity = 0.001;
 +
 +              setorigin (proj, w_shotorg);
 +              setsize(proj, '0 0 0', '0 0 0');
 +
 +              if(WEP_CVAR_SEC(crylink, spreadtype) == 1)
 +              {
 +                      s = '0 0 0';
 +                      if (counter == 0)
 +                              s = '0 0 0';
 +                      else
 +                      {
 +                              makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
 +                              s_y = v_forward_x;
 +                              s_z = v_forward_y;
 +                      }
 +                      s = s * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor;
 +                      s = w_shotdir + right * s_y + up * s_z;
 +              }
 +              else
 +              {
 +                      s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor);
 +              }
 +
 +              W_SetupProjectileVelocityEx(proj, s, v_up, WEP_CVAR_SEC(crylink, speed), 0, 0, 0, FALSE);
 +              proj.touch = W_Crylink_Touch;
 +              proj.think = W_Crylink_Fadethink;
 +              if(counter == (shots - 1) / 2)
 +              {
 +                      proj.fade_time = time + WEP_CVAR_SEC(crylink, middle_lifetime);
 +                      proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, middle_fadetime);
 +                      proj.nextthink = time + WEP_CVAR_SEC(crylink, middle_lifetime) + WEP_CVAR_SEC(crylink, middle_fadetime);
 +              }
 +              else
 +              {
 +                      proj.fade_time = time + WEP_CVAR_SEC(crylink, other_lifetime);
 +                      proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, other_fadetime);
 +                      proj.nextthink = time + WEP_CVAR_SEC(crylink, other_lifetime) + WEP_CVAR_SEC(crylink, other_fadetime);
 +              }
 +              proj.teleport_time = time + WEP_CVAR_SEC(crylink, joindelay);
 +              proj.cnt = WEP_CVAR_SEC(crylink, bounces);
 +              //proj.scale = 1 + 1 * proj.cnt;
 +
 +              proj.angles = vectoangles (proj.velocity);
 +
 +              //proj.glow_size = 20;
 +
 +              proj.flags = FL_PROJECTILE;
 +        proj.missile_flags = MIF_SPLASH;
 +        
 +              CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE);
 +
 +              other = proj; MUTATOR_CALLHOOK(EditProjectile);
 +      }
 +      if(WEP_CVAR_SEC(crylink, joinspread) != 0)
 +      {
 +              self.crylink_lastgroup = proj;
 +              W_Crylink_CheckLinks(proj);
 +              self.crylink_waitrelease = 2;
 +      }
 +}
 +
 +float w_crylink(float req)
 +{
 +      float ammo_amount;
 +      switch(req)
 +      {
 +              case WR_AIM:
 +              {
 +                      if (random() < 0.10)
 +                              self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), FALSE);
 +                      else
 +                              self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), FALSE);
 +                              
 +                      return TRUE;
 +              }
 +              case WR_THINK:
 +              {
 +                      if(autocvar_g_balance_crylink_reload_ammo && self.clip_load < min(autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_secondary_ammo)) // forced reload
 +                              WEP_ACTION(self.weapon, WR_RELOAD);
 +
 +                      if (self.BUTTON_ATCK)
 +                      {
 +                              if (self.crylink_waitrelease != 1)
 +                              if (weapon_prepareattack(0, WEP_CVAR_PRI(crylink, refire)))
 +                              {
 +                                      W_Crylink_Attack();
 +                                      weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(crylink, animtime), w_ready);
 +                              }
 +                      }
 +
 +                      if(self.BUTTON_ATCK2 && autocvar_g_balance_crylink_secondary)
 +                      {
 +                              if (self.crylink_waitrelease != 2)
 +                              if (weapon_prepareattack(1, WEP_CVAR_SEC(crylink, refire)))
 +                              {
 +                                      W_Crylink_Attack2();
 +                                      weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(crylink, animtime), w_ready);
 +                              }
 +                      }
 +
 +                      if ((self.crylink_waitrelease == 1 && !self.BUTTON_ATCK) || (self.crylink_waitrelease == 2 && !self.BUTTON_ATCK2))
 +                      {
 +                              if (!self.crylink_lastgroup || time > self.crylink_lastgroup.teleport_time)
 +                              {
 +                                      // fired and released now!
 +                                      if(self.crylink_lastgroup)
 +                                      {
 +                                              vector pos;
 +                                              entity linkjoineffect;
 +                                              float isprimary = (self.crylink_waitrelease == 1);
 +                                              
 +                                              pos = W_Crylink_LinkJoin(self.crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed));
 +
 +                                              linkjoineffect = spawn();
 +                                              linkjoineffect.think = W_Crylink_LinkJoinEffect_Think;
 +                                              linkjoineffect.classname = "linkjoineffect";
 +                                              linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
 +                                              linkjoineffect.owner = self;
 +                                              setorigin(linkjoineffect, pos);
 +                                      }
 +                                      self.crylink_waitrelease = 0;
 +                                      if(!w_crylink(WR_CHECKAMMO1) && !w_crylink(WR_CHECKAMMO2))
 +                                      if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
 +                                      {
 +                                              // ran out of ammo!
 +                                              self.cnt = WEP_CRYLINK;
 +                                              self.switchweapon = w_getbestweapon(self);
 +                                      }
 +                              }
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_model ("models/weapons/g_crylink.md3");
 +                      precache_model ("models/weapons/v_crylink.md3");
 +                      precache_model ("models/weapons/h_crylink.iqm");
 +                      precache_sound ("weapons/crylink_fire.wav");
 +                      precache_sound ("weapons/crylink_fire2.wav");
 +                      precache_sound ("weapons/crylink_linkjoin.wav");
 +                      WEP_SET_PROPS(CRYLINK_SETTINGS(crylink), WEP_CRYLINK)
 +                      return TRUE;
 +              }
 +              case WR_SETUP:
 +              {
 +                      self.current_ammo = ammo_cells;
 +                      return TRUE;
 +              }
 +              case WR_CHECKAMMO1:
 +              {
 +                      // don't "run out of ammo" and switch weapons while waiting for release
 +                      if(self.crylink_lastgroup && self.crylink_waitrelease)
 +                              return TRUE;
 +
 +                      ammo_amount = self.ammo_cells >= autocvar_g_balance_crylink_primary_ammo;
 +                      ammo_amount += self.(weapon_load[WEP_CRYLINK]) >= autocvar_g_balance_crylink_primary_ammo;
 +                      return ammo_amount;
 +              }
 +              case WR_CHECKAMMO2:
 +              {
 +                      // don't "run out of ammo" and switch weapons while waiting for release
 +                      if(self.crylink_lastgroup && self.crylink_waitrelease)
 +                              return TRUE;
 +
 +                      ammo_amount = self.ammo_cells >= autocvar_g_balance_crylink_secondary_ammo;
 +                      ammo_amount += self.(weapon_load[WEP_CRYLINK]) >= autocvar_g_balance_crylink_secondary_ammo;
 +                      return ammo_amount;
 +              }
 +              case WR_CONFIG:
 +              {
 +                      WEP_CONFIG_SETTINGS(CRYLINK_SETTINGS(crylink))
 +                      return TRUE;
 +              }
 +              case WR_RELOAD:
 +              {
 +                      W_Reload(min(autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_secondary_ammo), "weapons/reload.wav");
 +                      return TRUE;
 +              }
 +              case WR_SUICIDEMESSAGE:
 +              {
 +                      return WEAPON_CRYLINK_SUICIDE;
 +              }
 +              case WR_KILLMESSAGE:
 +              {
 +                      return WEAPON_CRYLINK_MURDER;
 +              }
 +      }
 +      return TRUE;
 +}
 +#endif
 +#ifdef CSQC
 +float w_crylink(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_IMPACTEFFECT:
 +              {
 +                      vector org2;
 +                      org2 = w_org + w_backoff * 2;
 +                      if(w_deathtype & HITTYPE_SECONDARY)
 +                      {
 +                              pointparticles(particleeffectnum("crylink_impact"), org2, '0 0 0', 1);
 +                              if(!w_issilent)
 +                                      sound(self, CH_SHOTS, "weapons/crylink_impact2.wav", VOL_BASE, ATTN_NORM);
 +                      }
 +                      else
 +                      {
 +                              pointparticles(particleeffectnum("crylink_impactbig"), org2, '0 0 0', 1);
 +                              if(!w_issilent)
 +                                      sound(self, CH_SHOTS, "weapons/crylink_impact.wav", VOL_BASE, ATTN_NORM);
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_sound("weapons/crylink_impact2.wav");
 +                      precache_sound("weapons/crylink_impact.wav");
 +                      return TRUE;
 +              }
 +      }
 +      return TRUE;
 +}
 +#endif
 +#endif
index 46f4384dd8f79e0c9aa7e69def7686897a1763c1,0000000000000000000000000000000000000000..b764d942ddf866c0753a20849c6a3a1ab1f921fa
mode 100644,000000..100644
--- /dev/null
@@@ -1,551 -1,0 +1,551 @@@
-                       if(IsDifferentTeam(self.realowner, other))
 +#ifdef REGISTER_WEAPON
 +REGISTER_WEAPON(
 +/* WEP_##id */ DEVASTATOR,
 +/* function */ W_Devastator,
 +/* ammotype */ IT_ROCKETS,
 +/* impulse  */ 9,
 +/* flags    */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
 +/* rating   */ BOT_PICKUP_RATING_HIGH,
 +/* model    */ "rl",
 +/* netname  */ "devastator",
 +/* fullname */ _("Devastator")
 +);
 +
 +#define DEVASTATOR_SETTINGS(weapon) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, ammo) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, animtime) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, damage) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, damageforcescale) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, detonatedelay) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, edgedamage) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, force) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, guidedelay) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, guidegoal) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, guiderate) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, guideratedelay) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, guidestop) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, health) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, lifetime) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, radius) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, refire) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, remote_damage) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, remote_edgedamage) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, remote_force) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, remote_radius) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, speed) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, speedaccel) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, speedstart) \
 +      WEP_ADD_PROP(weapon, reloading_ammo, reload_ammo) \
 +      WEP_ADD_PROP(weapon, reloading_time, reload_time) \
 +      WEP_ADD_PROP(weapon, switchdelay_raise, switchdelay_raise) \
 +      WEP_ADD_PROP(weapon, switchdelay_drop, switchdelay_drop)
 +
 +#ifdef SVQC
 +DEVASTATOR_SETTINGS(devastator)
 +.float rl_release;
 +.float rl_detonate_later;
 +#endif
 +#else
 +#ifdef SVQC
 +void spawnfunc_weapon_devastator() { weapon_defaultspawnfunc(WEP_DEVASTATOR); }
 +void spawnfunc_weapon_rocketlauncher() { spawnfunc_weapon_devastator(); }
 +
 +void W_Devastator_Unregister()
 +{
 +      if(self.realowner && self.realowner.lastrocket == self)
 +      {
 +              self.realowner.lastrocket = world;
 +              // self.realowner.rl_release = 1;
 +      }
 +}
 +
 +void W_Devastator_Explode()
 +{
 +      W_Devastator_Unregister();
 +
 +      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(devastator, damage), WEP_CVAR(devastator, edgedamage), WEP_CVAR(devastator, radius), world, world, WEP_CVAR(devastator, force), self.projectiledeathtype, other);
 +
 +      if (self.realowner.weapon == WEP_DEVASTATOR)
 +      {
 +              if(self.realowner.ammo_rockets < WEP_CVAR(devastator, ammo))
 +              {
 +                      self.realowner.cnt = WEP_DEVASTATOR;
 +                      ATTACK_FINISHED(self.realowner) = time;
 +                      self.realowner.switchweapon = w_getbestweapon(self.realowner);
 +              }
 +      }
 +      remove (self);
 +}
 +
 +void W_Devastator_DoRemoteExplode()
 +{
 +      W_Devastator_Unregister();
 +
 +      self.event_damage = func_null;
 +      self.takedamage = DAMAGE_NO;
 +
 +      RadiusDamage (self, self.realowner, WEP_CVAR(devastator, remote_damage), WEP_CVAR(devastator, remote_edgedamage), WEP_CVAR(devastator, remote_radius), world, world, WEP_CVAR(devastator, remote_force), self.projectiledeathtype | HITTYPE_BOUNCE, world);
 +
 +      if (self.realowner.weapon == WEP_DEVASTATOR)
 +      {
 +              if(self.realowner.ammo_rockets < WEP_CVAR(devastator, ammo))
 +              {
 +                      self.realowner.cnt = WEP_DEVASTATOR;
 +                      ATTACK_FINISHED(self.realowner) = time;
 +                      self.realowner.switchweapon = w_getbestweapon(self.realowner);
 +              }
 +      }
 +      remove (self);
 +}
 +
 +void W_Devastator_RemoteExplode()
 +{
 +      if(self.realowner.deadflag == DEAD_NO)
 +      if(self.realowner.lastrocket)
 +      {
 +              if((self.spawnshieldtime >= 0)
 +                      ? (time >= self.spawnshieldtime) // timer
 +                      : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > WEP_CVAR(devastator, remote_radius)) // safety device
 +              )
 +              {
 +                      W_Devastator_DoRemoteExplode();
 +              }
 +      }
 +}
 +
 +vector W_Devastator_SteerTo(vector thisdir, vector goaldir, float maxturn_cos)
 +{
 +      if(thisdir * goaldir > maxturn_cos)
 +              return goaldir;
 +      if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite
 +              return thisdir; // refuse to guide (better than letting a numerical error happen)
 +      float f, m2;
 +      vector v;
 +      // solve:
 +      //   g = normalize(thisdir + goaldir * X)
 +      //   thisdir * g = maxturn
 +      //
 +      //   gg = thisdir + goaldir * X
 +      //   (thisdir * gg)^2 = maxturn^2 * (gg * gg)
 +      //
 +      //   (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir)
 +      f = thisdir * goaldir;
 +      //   (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f)
 +      //   0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1)
 +      m2 = maxturn_cos * maxturn_cos;
 +      v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1);
 +      return normalize(thisdir + goaldir * v_y); // the larger solution!
 +}
 +// assume thisdir == -goaldir:
 +//   f == -1
 +//   v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1)
 +//   (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0
 +//   x^2 - 2 * x + 1 = 0
 +//   (x - 1)^2 = 0
 +//   x = 1
 +//   normalize(thisdir + goaldir)
 +//   normalize(0)
 +
 +void W_Devastator_Think (void)
 +{
 +      vector desireddir, olddir, newdir, desiredorigin, goal;
 +#if 0
 +      float cosminang, cosmaxang, cosang;
 +#endif
 +      float velspeed, f;
 +      self.nextthink = time;
 +      if (time > self.cnt)
 +      {
 +              other = world;
 +              self.projectiledeathtype |= HITTYPE_BOUNCE;
 +              W_Devastator_Explode ();
 +              return;
 +      }
 +
 +      // accelerate
 +      makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0');
 +      velspeed = WEP_CVAR(devastator, speed) * g_weaponspeedfactor - (self.velocity * v_forward);
 +      if (velspeed > 0)
 +              self.velocity = self.velocity + v_forward * min(WEP_CVAR(devastator, speedaccel) * g_weaponspeedfactor * frametime, velspeed);
 +
 +      // laser guided, or remote detonation
 +      if (self.realowner.weapon == WEP_DEVASTATOR)
 +      {
 +              if(self == self.realowner.lastrocket)
 +              if not(self.realowner.rl_release)
 +              if not(self.BUTTON_ATCK2)
 +              if(WEP_CVAR(devastator, guiderate))
 +              if(time > self.pushltime)
 +              if(self.realowner.deadflag == DEAD_NO)
 +              {
 +                      f = WEP_CVAR(devastator, guideratedelay);
 +                      if(f)
 +                              f = bound(0, (time - self.pushltime) / f, 1);
 +                      else
 +                              f = 1;
 +
 +                      velspeed = vlen(self.velocity);
 +
 +                      makevectors(self.realowner.v_angle);
 +                      desireddir = WarpZone_RefSys_TransformVelocity(self.realowner, self, v_forward);
 +                      desiredorigin = WarpZone_RefSys_TransformOrigin(self.realowner, self, self.realowner.origin + self.realowner.view_ofs);
 +                      olddir = normalize(self.velocity);
 +
 +                      // now it gets tricky... we want to move like some curve to approximate the target direction
 +                      // but we are limiting the rate at which we can turn!
 +                      goal = desiredorigin + ((self.origin - desiredorigin) * desireddir + WEP_CVAR(devastator, guidegoal)) * desireddir;
 +                      newdir = W_Devastator_SteerTo(olddir, normalize(goal - self.origin), cos(WEP_CVAR(devastator, guiderate) * f * frametime * DEG2RAD));
 +
 +                      self.velocity = newdir * velspeed;
 +                      self.angles = vectoangles(self.velocity);
 +
 +                      if(!self.count)
 +                      {
 +                              pointparticles(particleeffectnum("rocket_guide"), self.origin, self.velocity, 1);
 +                              // TODO add a better sound here
 +                              sound (self.realowner, CH_WEAPON_B, "weapons/rocket_mode.wav", VOL_BASE, ATTN_NORM);
 +                              self.count = 1;
 +                      }
 +              }
 +
 +              if(self.rl_detonate_later)
 +                      W_Devastator_RemoteExplode();
 +      }
 +
 +      if(self.csqcprojectile_clientanimate == 0)
 +              UpdateCSQCProjectile(self);
 +}
 +
 +void W_Devastator_Touch (void)
 +{
 +      if(WarpZone_Projectile_Touch())
 +      {
 +              if(wasfreed(self))
 +                      W_Devastator_Unregister();
 +              return;
 +      }
 +      W_Devastator_Unregister();
 +      W_Devastator_Explode ();
 +}
 +
 +void W_Devastator_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 +{
 +      if (self.health <= 0)
 +              return;
 +      
 +      if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
 +              return; // g_projectiles_damage says to halt
 +              
 +      self.health = self.health - damage;
 +      self.angles = vectoangles(self.velocity);
 +      
 +      if (self.health <= 0)
 +              W_PrepareExplosionByDamage(attacker, W_Devastator_Explode);
 +}
 +
 +void W_Devastator_Attack (void)
 +{
 +      entity missile;
 +      entity flash;
 +
 +      W_DecreaseAmmo(ammo_rockets, WEP_CVAR(devastator, ammo), WEP_CVAR(devastator, reload_ammo));
 +
 +      W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, WEP_CVAR(devastator, damage));
 +      pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
 +
 +      missile = WarpZone_RefSys_SpawnSameRefSys(self);
 +      missile.owner = missile.realowner = self;
 +      self.lastrocket = missile;
 +      if(WEP_CVAR(devastator, detonatedelay) >= 0)
 +              missile.spawnshieldtime = time + WEP_CVAR(devastator, detonatedelay);
 +      else
 +              missile.spawnshieldtime = -1;
 +      missile.pushltime = time + WEP_CVAR(devastator, guidedelay);
 +      missile.classname = "rocket";
 +      missile.bot_dodge = TRUE;
 +      missile.bot_dodgerating = WEP_CVAR(devastator, damage) * 2; // * 2 because it can be detonated inflight which makes it even more dangerous
 +
 +      missile.takedamage = DAMAGE_YES;
 +      missile.damageforcescale = WEP_CVAR(devastator, damageforcescale);
 +      missile.health = WEP_CVAR(devastator, health);
 +      missile.event_damage = W_Devastator_Damage;
 +      missile.damagedbycontents = TRUE;
 +
 +      missile.movetype = MOVETYPE_FLY;
 +      PROJECTILE_MAKETRIGGER(missile);
 +      missile.projectiledeathtype = WEP_DEVASTATOR;
 +      setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
 +
 +      setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
 +      W_SetupProjectileVelocity(missile, WEP_CVAR(devastator, speedstart), 0);
 +      missile.angles = vectoangles (missile.velocity);
 +
 +      missile.touch = W_Devastator_Touch;
 +      missile.think = W_Devastator_Think;
 +      missile.nextthink = time;
 +      missile.cnt = time + WEP_CVAR(devastator, lifetime);
 +      missile.flags = FL_PROJECTILE;
 +      missile.missile_flags = MIF_SPLASH; 
 +
 +      CSQCProjectile(missile, WEP_CVAR(devastator, guiderate) == 0 && WEP_CVAR(devastator, speedaccel) == 0, PROJECTILE_ROCKET, FALSE); // because of fly sound
 +
 +      // 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 = missile; MUTATOR_CALLHOOK(EditProjectile);
 +}
 +
 +float W_Devastator(float req)
 +{
 +      entity rock;
 +      float rockfound;
 +      float ammo_amount;
 +      switch(req)
 +      {
 +              case WR_AIM: // WEAPONTODO: rewrite this, it's WAY too complicated for what it should be
 +              {
 +                      // aim and decide to fire if appropriate
 +                      self.BUTTON_ATCK = bot_aim(WEP_CVAR(devastator, speed), 0, WEP_CVAR(devastator, lifetime), FALSE);
 +                      if(skill >= 2) // skill 0 and 1 bots won't detonate rockets!
 +                      {
 +                              // decide whether to detonate rockets
 +                              entity missile, targetlist, targ;
 +                              float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
 +                              float selfdamage, teamdamage, enemydamage;
 +                              edgedamage = WEP_CVAR(devastator, edgedamage);
 +                              coredamage = WEP_CVAR(devastator, damage);
 +                              edgeradius = WEP_CVAR(devastator, radius);
 +                              recipricoledgeradius = 1 / edgeradius;
 +                              selfdamage = 0;
 +                              teamdamage = 0;
 +                              enemydamage = 0;
 +                              targetlist = findchainfloat(bot_attack, TRUE);
 +                              missile = find(world, classname, "rocket");
 +                              while (missile)
 +                              {
 +                                      if (missile.realowner != self)
 +                                      {
 +                                              missile = find(missile, classname, "rocket");
 +                                              continue;
 +                                      }
 +                                      targ = targetlist;
 +                                      while (targ)
 +                                      {
 +                                              d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - missile.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;
 +                                      }
 +                                      missile = find(missile, classname, "rocket");
 +                              }
 +                              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;
 +
 +                              missile = find(world, classname, "rocket");
 +                              while (missile)
 +                              {
 +                                      if (missile.realowner != self)
 +                                      {
 +                                              missile = find(missile, classname, "rocket");
 +                                              continue;
 +                                      }
 +                                      makevectors(missile.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(missile.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 rocket spawnfunc_light to see if the rocket gets near a player
 +                                              if(v_forward * normalize(missile.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");
 +                                      }
 +
 +                                      missile = find(missile, classname, "rocket");
 +                              }
 +                              // 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(WEP_CVAR(devastator, reload_ammo) && self.clip_load < WEP_CVAR(devastator, ammo)) // forced reload
 +                              WEP_ACTION(self.weapon, WR_RELOAD);
 +                      else
 +                      {
 +                              if (self.BUTTON_ATCK)
 +                              {
 +                                      if(self.rl_release || WEP_CVAR(devastator, guidestop))
 +                                      if(weapon_prepareattack(0, WEP_CVAR(devastator, refire)))
 +                                      {
 +                                              W_Devastator_Attack();
 +                                              weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(devastator, animtime), w_ready);
 +                                              self.rl_release = 0;
 +                                      }
 +                              }
 +                              else
 +                                      self.rl_release = 1;
 +
 +                              if (self.BUTTON_ATCK2)
 +                              {
 +                                      rockfound = 0;
 +                                      for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.realowner == self)
 +                                      {
 +                                              if(!rock.rl_detonate_later)
 +                                              {
 +                                                      rock.rl_detonate_later = TRUE;
 +                                                      rockfound = 1;
 +                                              }
 +                                      }
 +                                      if(rockfound)
 +                                              sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM);
 +                              }
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      if(autocvar_sv_precacheweapons)
 +                      {
 +                              precache_model("models/flash.md3");
 +                              precache_model("models/weapons/g_rl.md3");
 +                              precache_model("models/weapons/v_rl.md3");
 +                              precache_model("models/weapons/h_rl.iqm");
 +                              precache_sound("weapons/rocket_det.wav");
 +                              precache_sound("weapons/rocket_fire.wav");
 +                              precache_sound("weapons/rocket_mode.wav");
 +                      }
 +                      WEP_SET_PROPS(DEVASTATOR_SETTINGS(devastator), WEP_DEVASTATOR)
 +                      return TRUE;
 +              }
 +              case WR_SETUP:
 +              {
 +                      self.current_ammo = ammo_rockets;
 +                      self.rl_release = 1;
 +                      return TRUE;
 +              }
 +              case WR_CHECKAMMO1:
 +              {
 +                      // don't switch while guiding a missile
 +                      if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_DEVASTATOR)
 +                      {
 +                              ammo_amount = FALSE;
 +                              if(WEP_CVAR(devastator, reload_ammo))
 +                              {
 +                                      if(self.ammo_rockets < WEP_CVAR(devastator, ammo) && self.(weapon_load[WEP_DEVASTATOR]) < WEP_CVAR(devastator, ammo))
 +                                              ammo_amount = TRUE;
 +                              }
 +                              else if(self.ammo_rockets < WEP_CVAR(devastator, ammo))
 +                                      ammo_amount = TRUE;
 +                              return !ammo_amount;
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_CHECKAMMO2:
 +              {
 +                      return FALSE;
 +              }
 +              case WR_CONFIG:
 +              {
 +                      WEP_CONFIG_SETTINGS(DEVASTATOR_SETTINGS(devastator))
 +                      return TRUE;
 +              }
 +              case WR_RESETPLAYER:
 +              {
 +                      self.rl_release = 0;
 +                      return TRUE;
 +              }
 +              case WR_RELOAD:
 +              {
 +                      W_Reload(WEP_CVAR(devastator, ammo), "weapons/reload.wav");
 +                      return TRUE;
 +              }
 +              case WR_SUICIDEMESSAGE:
 +              {
 +                      return WEAPON_ROCKETLAUNCHER_SUICIDE;
 +              }
 +              case WR_KILLMESSAGE:
 +              {
 +                      if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
 +                              return WEAPON_ROCKETLAUNCHER_MURDER_SPLASH;
 +                      else
 +                              return WEAPON_ROCKETLAUNCHER_MURDER_DIRECT;
 +              }
 +      }
 +      return TRUE;
 +}
 +#endif
 +#ifdef CSQC
 +float W_Devastator(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/rocket_impact.wav", VOL_BASE, ATTN_NORM);
 +                              
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_sound("weapons/rocket_impact.wav");
 +                      return TRUE;
 +              }
 +      }
 +      return TRUE;
 +}
 +#endif
 +#endif
index 2b79a92b2d56222d5bec4d0f372d1af877ab6c0a,0000000000000000000000000000000000000000..55843fcae5df76c8117232c0cdecbb8d1e9e9939
mode 100644,000000..100644
--- /dev/null
@@@ -1,638 -1,0 +1,638 @@@
-                       if(IsDifferentTeam(self.realowner, other))
 +#ifdef REGISTER_WEAPON
 +REGISTER_WEAPON(
 +/* WEP_##id */ ELECTRO,
 +/* function */ w_electro,
 +/* ammotype */ IT_CELLS,
 +/* impulse  */ 5,
 +/* flags    */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
 +/* rating   */ BOT_PICKUP_RATING_MID,
 +/* model    */ "electro",
 +/* netname  */ "electro",
 +/* fullname */ _("Electro")
 +);
 +
 +#ifdef SVQC
 +void ElectroInit();
 +vector electro_shotorigin[4];
 +#endif
 +#else
 +#ifdef SVQC
 +void spawnfunc_weapon_electro() { weapon_defaultspawnfunc(WEP_ELECTRO); }
 +
 +.float electro_count;
 +.float electro_secondarytime;
 +
 +void W_Plasma_Explode_Combo (void);
 +
 +void W_Plasma_TriggerCombo(vector org, float rad, entity own)
 +{
 +      entity e;
 +      e = WarpZone_FindRadius(org, rad, TRUE);
 +      while (e)
 +      {
 +              if (e.classname == "plasma")
 +              {
 +                      // change owner to whoever caused the combo explosion
 +                      e.realowner = own;
 +                      e.takedamage = DAMAGE_NO;
 +                      e.classname = "plasma_chain";
 +                      e.think = W_Plasma_Explode_Combo;
 +                      e.nextthink = time + vlen(e.WarpZone_findradius_dist) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler
 +              }
 +              e = e.chain;
 +      }
 +}
 +
 +void W_Plasma_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_ELECTROBITCH);
 +
 +      self.event_damage = func_null;
 +      self.takedamage = DAMAGE_NO;
 +      if (self.movetype == MOVETYPE_BOUNCE)
 +      {
 +              RadiusDamage (self, self.realowner, autocvar_g_balance_electro_secondary_damage, autocvar_g_balance_electro_secondary_edgedamage, autocvar_g_balance_electro_secondary_radius, world, world, autocvar_g_balance_electro_secondary_force, self.projectiledeathtype, other);
 +      }
 +      else
 +      {
 +              W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_primary_comboradius, self.realowner);
 +              RadiusDamage (self, self.realowner, autocvar_g_balance_electro_primary_damage, autocvar_g_balance_electro_primary_edgedamage, autocvar_g_balance_electro_primary_radius, world, world, autocvar_g_balance_electro_primary_force, self.projectiledeathtype, other);
 +      }
 +
 +      remove (self);
 +}
 +
 +void W_Plasma_Explode_Combo (void)
 +{
 +      W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_combo_comboradius, self.realowner);
 +
 +      self.event_damage = func_null;
 +      RadiusDamage (self, self.realowner, autocvar_g_balance_electro_combo_damage, autocvar_g_balance_electro_combo_edgedamage, autocvar_g_balance_electro_combo_radius, world, world, autocvar_g_balance_electro_combo_force, WEP_ELECTRO | HITTYPE_BOUNCE, world); // use THIS type for a combo because primary can't bounce
 +
 +      remove (self);
 +}
 +
 +void W_Plasma_Touch (void)
 +{
 +      //self.velocity = self.velocity  * 0.1;
 +
 +      PROJECTILE_TOUCH;
 +      if (other.takedamage == DAMAGE_AIM) {
 +              W_Plasma_Explode ();
 +      } else {
 +              //UpdateCSQCProjectile(self);
 +              spamsound (self, CH_SHOTS, "weapons/electro_bounce.wav", VOL_BASE, ATTEN_NORM);
 +              self.projectiledeathtype |= HITTYPE_BOUNCE;
 +      }
 +}
 +
 +void W_Plasma_TouchExplode (void)
 +{
 +      PROJECTILE_TOUCH;
 +      W_Plasma_Explode ();
 +}
 +
 +void W_Plasma_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 +{
 +      if(self.health <= 0)
 +              return;
 +
 +      // note: combos are usually triggered by W_Plasma_TriggerCombo, not damage
 +      float is_combo = (inflictor.classname == "plasma_chain" || inflictor.classname == "plasma_prim");
 +      
 +      if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_combo ? 1 : -1)))
 +              return; // g_projectiles_damage says to halt    
 +      
 +      self.health = self.health - damage;
 +      if (self.health <= 0)
 +      {
 +              self.takedamage = DAMAGE_NO;
 +              self.nextthink = time;
 +              if (is_combo)
 +              {
 +                      // change owner to whoever caused the combo explosion
 +                      self.realowner = inflictor.realowner;
 +                      self.classname = "plasma_chain";
 +                      self.think = W_Plasma_Explode_Combo;
 +                      self.nextthink = time + min(autocvar_g_balance_electro_combo_radius, vlen(self.origin - inflictor.origin)) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler
 +                              //                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ bounding the length, because inflictor may be in a galaxy far far away (warpzones)
 +              }
 +              else
 +              {
 +                      self.use = W_Plasma_Explode;
 +                      self.think = adaptor_think2use; // not _hittype_splash, as this runs "immediately"
 +              }
 +      }
 +}
 +
 +void W_Electro_Attack()
 +{
 +      entity proj;
 +
 +      W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_reload_ammo);
 +
 +      W_SetupShot_ProjectileSize (self, '0 0 -3', '0 0 -3', FALSE, 2, "weapons/electro_fire.wav", CH_WEAPON_A, autocvar_g_balance_electro_primary_damage);
 +
 +      pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
 +
 +      proj = spawn ();
 +      proj.classname = "plasma_prim";
 +      proj.owner = proj.realowner = self;
 +      proj.bot_dodge = TRUE;
 +      proj.bot_dodgerating = autocvar_g_balance_electro_primary_damage;
 +      proj.use = W_Plasma_Explode;
 +      proj.think = adaptor_think2use_hittype_splash;
 +      proj.nextthink = time + autocvar_g_balance_electro_primary_lifetime;
 +      PROJECTILE_MAKETRIGGER(proj);
 +      proj.projectiledeathtype = WEP_ELECTRO;
 +      setorigin(proj, w_shotorg);
 +
 +      proj.movetype = MOVETYPE_FLY;
 +      W_SETUPPROJECTILEVELOCITY(proj, g_balance_electro_primary);
 +      proj.angles = vectoangles(proj.velocity);
 +      proj.touch = W_Plasma_TouchExplode;
 +      setsize(proj, '0 0 -3', '0 0 -3');
 +      proj.flags = FL_PROJECTILE;
 +      proj.missile_flags = MIF_SPLASH;
 +
 +      CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO_BEAM, TRUE);
 +
 +      other = proj; MUTATOR_CALLHOOK(EditProjectile);
 +}
 +
 +void W_Electro_Attack2()
 +{
 +      entity proj;
 +
 +      W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_secondary_ammo, autocvar_g_balance_electro_reload_ammo);
 +
 +      W_SetupShot_ProjectileSize (self, '0 0 -4', '0 0 -4', FALSE, 2, "weapons/electro_fire2.wav", CH_WEAPON_A, autocvar_g_balance_electro_secondary_damage);
 +
 +      w_shotdir = v_forward; // no TrueAim for grenades please
 +
 +      pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
 +
 +      proj = spawn ();
 +      proj.classname = "plasma";
 +      proj.owner = proj.realowner = self;
 +      proj.use = W_Plasma_Explode;
 +      proj.think = adaptor_think2use_hittype_splash;
 +      proj.bot_dodge = TRUE;
 +      proj.bot_dodgerating = autocvar_g_balance_electro_secondary_damage;
 +      proj.nextthink = time + autocvar_g_balance_electro_secondary_lifetime;
 +      PROJECTILE_MAKETRIGGER(proj);
 +      proj.projectiledeathtype = WEP_ELECTRO | HITTYPE_SECONDARY;
 +      setorigin(proj, w_shotorg);
 +
 +      //proj.glow_size = 50;
 +      //proj.glow_color = 45;
 +      proj.movetype = MOVETYPE_BOUNCE;
 +      W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_electro_secondary);
 +      proj.touch = W_Plasma_Touch;
 +      setsize(proj, '0 0 -4', '0 0 -4');
 +      proj.takedamage = DAMAGE_YES;
 +      proj.damageforcescale = autocvar_g_balance_electro_secondary_damageforcescale;
 +      proj.health = autocvar_g_balance_electro_secondary_health;
 +      proj.event_damage = W_Plasma_Damage;
 +      proj.flags = FL_PROJECTILE;
 +      proj.damagedbycontents = (autocvar_g_balance_electro_secondary_damagedbycontents);
 +
 +      proj.bouncefactor = autocvar_g_balance_electro_secondary_bouncefactor;
 +      proj.bouncestop = autocvar_g_balance_electro_secondary_bouncestop;
 +      proj.missile_flags = MIF_SPLASH | MIF_ARC;
 +
 +#if 0
 +      entity p2;
 +      p2 = spawn();
 +      copyentity(proj, p2);
 +      setmodel(p2, "models/ebomb.mdl");
 +      setsize(p2, proj.mins, proj.maxs);
 +#endif
 +
 +      CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, FALSE); // no culling, it has sound
 +
 +      other = proj; MUTATOR_CALLHOOK(EditProjectile);
 +}
 +
 +.vector hook_start, hook_end;
 +float lgbeam_send(entity to, float sf)
 +{
 +      WriteByte(MSG_ENTITY, ENT_CLIENT_ELECTRO_BEAM);
 +      sf = sf & 0x7F;
 +      if(sound_allowed(MSG_BROADCAST, self.realowner))
 +              sf |= 0x80;
 +      WriteByte(MSG_ENTITY, sf);
 +      if(sf & 1)
 +      {
 +              WriteByte(MSG_ENTITY, num_for_edict(self.realowner));
 +              WriteCoord(MSG_ENTITY, autocvar_g_balance_electro_primary_range);
 +      }
 +      if(sf & 2)
 +      {
 +              WriteCoord(MSG_ENTITY, self.hook_start_x);
 +              WriteCoord(MSG_ENTITY, self.hook_start_y);
 +              WriteCoord(MSG_ENTITY, self.hook_start_z);
 +      }
 +      if(sf & 4)
 +      {
 +              WriteCoord(MSG_ENTITY, self.hook_end_x);
 +              WriteCoord(MSG_ENTITY, self.hook_end_y);
 +              WriteCoord(MSG_ENTITY, self.hook_end_z);
 +      }
 +      return TRUE;
 +}
 +.entity lgbeam;
 +.float prevlgfire;
 +float lgbeam_checkammo()
 +{
 +      if(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO)
 +              return TRUE;
 +      else if(autocvar_g_balance_electro_reload_ammo)
 +              return self.realowner.clip_load > 0;
 +      else
 +              return self.realowner.ammo_cells > 0;
 +}
 +
 +entity lgbeam_owner_ent;
 +void lgbeam_think()
 +{
 +      entity owner_player;
 +      owner_player = self.realowner;
 +
 +      owner_player.prevlgfire = time;
 +      if (self != owner_player.lgbeam)
 +      {
 +              remove(self);
 +              return;
 +      }
 +
 +      if (owner_player.weaponentity.state != WS_INUSE || !lgbeam_checkammo() || owner_player.deadflag != DEAD_NO || !owner_player.BUTTON_ATCK || owner_player.freezetag_frozen)
 +      {
 +              if(self == owner_player.lgbeam)
 +                      owner_player.lgbeam = world;
 +              remove(self);
 +              return;
 +      }
 +
 +      self.nextthink = time;
 +
 +      makevectors(owner_player.v_angle);
 +
 +      float dt, f;
 +      dt = frametime;
 +
 +      // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
 +      if not(owner_player.items & IT_UNLIMITED_WEAPON_AMMO)
 +      {
 +              if(autocvar_g_balance_electro_primary_ammo)
 +              {
 +                      if(autocvar_g_balance_electro_reload_ammo)
 +                      {
 +                              dt = min(dt, owner_player.clip_load / autocvar_g_balance_electro_primary_ammo);
 +                              owner_player.clip_load = max(0, owner_player.clip_load - autocvar_g_balance_electro_primary_ammo * frametime);
 +                              owner_player.(weapon_load[WEP_ELECTRO]) = owner_player.clip_load;
 +                      }
 +                      else
 +                      {
 +                              dt = min(dt, owner_player.ammo_cells / autocvar_g_balance_electro_primary_ammo);
 +                              owner_player.ammo_cells = max(0, owner_player.ammo_cells - autocvar_g_balance_electro_primary_ammo * frametime);
 +                      }
 +              }
 +      }
 +
 +      W_SetupShot_Range(owner_player, TRUE, 0, "", 0, autocvar_g_balance_electro_primary_damage * dt, autocvar_g_balance_electro_primary_range);
 +      if(!lgbeam_owner_ent)
 +      {
 +              lgbeam_owner_ent = spawn();
 +              lgbeam_owner_ent.classname = "lgbeam_owner_ent";
 +      }
 +      WarpZone_traceline_antilag(lgbeam_owner_ent, w_shotorg, w_shotend, MOVE_NORMAL, lgbeam_owner_ent, ANTILAG_LATENCY(owner_player));
 +
 +      // apply the damage
 +      if(trace_ent)
 +      {
 +              vector force;
 +              force = w_shotdir * autocvar_g_balance_electro_primary_force + '0 0 1' * autocvar_g_balance_electro_primary_force_up;
 +
 +              f = ExponentialFalloff(autocvar_g_balance_electro_primary_falloff_mindist, autocvar_g_balance_electro_primary_falloff_maxdist, autocvar_g_balance_electro_primary_falloff_halflifedist, vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg));
 +
 +              if(accuracy_isgooddamage(owner_player, trace_ent))
 +                      accuracy_add(owner_player, WEP_ELECTRO, 0, autocvar_g_balance_electro_primary_damage * dt * f);
 +              Damage (trace_ent, owner_player, owner_player, autocvar_g_balance_electro_primary_damage * dt * f, WEP_ELECTRO, trace_endpos, force * dt);
 +      }
 +      W_Plasma_TriggerCombo(trace_endpos, autocvar_g_balance_electro_primary_comboradius, owner_player);
 +
 +      // draw effect
 +      if(w_shotorg != self.hook_start)
 +      {
 +              self.SendFlags |= 2;
 +              self.hook_start = w_shotorg;
 +      }
 +      if(w_shotend != self.hook_end)
 +      {
 +              self.SendFlags |= 4;
 +              self.hook_end = w_shotend;
 +      }
 +}
 +
 +// experimental lightning gun
 +void W_Electro_Attack3 (void)
 +{
 +      // only play fire sound if 0.5 sec has passed since player let go the fire button
 +      if(time - self.prevlgfire > 0.5)
 +              sound (self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTEN_NORM);
 +
 +      entity beam, oldself;
 +
 +      self.lgbeam = beam = spawn();
 +      beam.classname = "lgbeam";
 +      beam.solid = SOLID_NOT;
 +      beam.think = lgbeam_think;
 +      beam.owner = beam.realowner = self;
 +      beam.movetype = MOVETYPE_NONE;
 +      beam.shot_spread = 0;
 +      beam.bot_dodge = TRUE;
 +      beam.bot_dodgerating = autocvar_g_balance_electro_primary_damage;
 +      Net_LinkEntity(beam, FALSE, 0, lgbeam_send);
 +
 +      oldself = self;
 +      self = beam;
 +      self.think();
 +      self = oldself;
 +}
 +
 +void ElectroInit()
 +{
 +      WEP_ACTION(WEP_ELECTRO, WR_INIT);
 +      electro_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 1);
 +      electro_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 2);
 +      electro_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 3);
 +      electro_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 4);
 +}
 +
 +void w_electro_checkattack()
 +{
 +      if(self.electro_count > 1)
 +      if(self.BUTTON_ATCK2)
 +      if(weapon_prepareattack(1, -1))
 +      {
 +              W_Electro_Attack2();
 +              self.electro_count -= 1;
 +              weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack);
 +              return;
 +      }
 +
 +      w_ready();
 +}
 +
 +.float bot_secondary_electromooth;
 +.float BUTTON_ATCK_prev;
 +float w_electro(float req)
 +{
 +      float ammo_amount;
 +      switch(req)
 +      {
 +              case WR_AIM:
 +              {
 +                      self.BUTTON_ATCK=FALSE;
 +                      self.BUTTON_ATCK2=FALSE;
 +                      if(vlen(self.origin-self.enemy.origin) > 1000)
 +                              self.bot_secondary_electromooth = 0;
 +                      if(self.bot_secondary_electromooth == 0)
 +                      {
 +                              float shoot;
 +
 +                              if(autocvar_g_balance_electro_primary_speed)
 +                                      shoot = bot_aim(autocvar_g_balance_electro_primary_speed, 0, autocvar_g_balance_electro_primary_lifetime, FALSE);
 +                              else
 +                                      shoot = bot_aim(1000000, 0, 0.001, FALSE);
 +
 +                              if(shoot)
 +                              {
 +                                      self.BUTTON_ATCK = TRUE;
 +                                      if(random() < 0.01) self.bot_secondary_electromooth = 1;
 +                              }
 +                      }
 +                      else
 +                      {
 +                              if(bot_aim(autocvar_g_balance_electro_secondary_speed, autocvar_g_balance_mortar_secondary_speed_up, autocvar_g_balance_electro_secondary_lifetime, TRUE)) // WHAT THE ACTUAL FUUUUUUUUUCK?!?!? WEAPONTODO
 +                              {
 +                                      self.BUTTON_ATCK2 = TRUE;
 +                                      if(random() < 0.03) self.bot_secondary_electromooth = 0;
 +                              }
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_THINK:
 +              {
 +                      if(autocvar_g_balance_electro_reload_ammo) // forced reload
 +                      {
 +                              ammo_amount = 0;
 +                              if(autocvar_g_balance_electro_lightning)
 +                              {
 +                                      if(self.clip_load > 0)
 +                                              ammo_amount = 1;
 +                              }
 +                              else if(self.clip_load >= autocvar_g_balance_electro_primary_ammo)
 +                                      ammo_amount = 1;
 +                              if(self.clip_load >= autocvar_g_balance_electro_secondary_ammo)
 +                                      ammo_amount += 1;
 +
 +                              if(!ammo_amount)
 +                              {
 +                                      WEP_ACTION(self.weapon, WR_RELOAD);
 +                                      return FALSE;
 +                              }
 +                              
 +                              return TRUE;
 +                      }
 +                      if (self.BUTTON_ATCK)
 +                      {
 +                              if(autocvar_g_balance_electro_lightning)
 +                                      if(self.BUTTON_ATCK_prev)
 +                                              weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
 +
 +                              if (weapon_prepareattack(0, (autocvar_g_balance_electro_lightning ? 0 : autocvar_g_balance_electro_primary_refire)))
 +                              {
 +                                      if(autocvar_g_balance_electro_lightning)
 +                                      {
 +                                              if ((!self.lgbeam) || wasfreed(self.lgbeam))
 +                                              {
 +                                                      W_Electro_Attack3();
 +                                              }
 +                                              if(!self.BUTTON_ATCK_prev)
 +                                              {
 +                                                      weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
 +                                                      self.BUTTON_ATCK_prev = 1;
 +                                              }
 +                                      }
 +                                      else
 +                                      {
 +                                              W_Electro_Attack();
 +                                              weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
 +                                      }
 +                              }
 +                      } else {
 +                              if(autocvar_g_balance_electro_lightning)
 +                              {
 +                                      if (self.BUTTON_ATCK_prev != 0)
 +                                      {
 +                                              weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
 +                                              ATTACK_FINISHED(self) = time + autocvar_g_balance_electro_primary_refire * W_WeaponRateFactor();
 +                                      }
 +                                      self.BUTTON_ATCK_prev = 0;
 +                              }
 +
 +                              if (self.BUTTON_ATCK2)
 +                              {
 +                                      if (time >= self.electro_secondarytime)
 +                                      if (weapon_prepareattack(1, autocvar_g_balance_electro_secondary_refire))
 +                                      {
 +                                              W_Electro_Attack2();
 +                                              self.electro_count = autocvar_g_balance_electro_secondary_count;
 +                                              weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack);
 +                                              self.electro_secondarytime = time + autocvar_g_balance_electro_secondary_refire2 * W_WeaponRateFactor();
 +                                      }
 +                              }
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_model ("models/weapons/g_electro.md3");
 +                      precache_model ("models/weapons/v_electro.md3");
 +                      precache_model ("models/weapons/h_electro.iqm");
 +                      precache_sound ("weapons/electro_bounce.wav");
 +                      precache_sound ("weapons/electro_fire.wav");
 +                      precache_sound ("weapons/electro_fire2.wav");
 +                      precache_sound ("weapons/electro_impact.wav");
 +                      precache_sound ("weapons/electro_impact_combo.wav");
 +                      
 +                      if(autocvar_g_balance_electro_lightning)
 +                              precache_sound ("weapons/lgbeam_fire.wav");
 +                              
 +                      return TRUE;
 +              }
 +              case WR_SETUP:
 +              {
 +                      self.current_ammo = ammo_cells;
 +                      return TRUE;
 +              }
 +              case WR_CHECKAMMO1:
 +              {
 +                      if(autocvar_g_balance_electro_lightning)
 +                      {
 +                              if(!autocvar_g_balance_electro_primary_ammo)
 +                                      ammo_amount = 1;
 +                              else
 +                                      ammo_amount = self.ammo_cells > 0;
 +                              ammo_amount += self.(weapon_load[WEP_ELECTRO]) > 0;
 +                      }
 +                      else
 +                      {
 +                              ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_primary_ammo;
 +                              ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_primary_ammo;
 +                      }
 +                      return ammo_amount;
 +              }
 +              case WR_CHECKAMMO2:
 +              {
 +                      if(autocvar_g_balance_electro_combo_safeammocheck) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
 +                      {
 +                              ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo;
 +                              ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo;
 +                      }
 +                      else
 +                      {
 +                              ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo;
 +                              ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo;
 +                      }
 +                      return ammo_amount;
 +              }
 +              case WR_RESETPLAYER:
 +              {
 +                      self.electro_secondarytime = time;
 +                      return TRUE;
 +              }
 +              case WR_RELOAD:
 +              {
 +                      W_Reload(min(autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_secondary_ammo), "weapons/reload.wav");
 +                      return TRUE;
 +              }
 +              case WR_SUICIDEMESSAGE:
 +              {
 +                      if(w_deathtype & HITTYPE_SECONDARY)
 +                              return WEAPON_ELECTRO_SUICIDE_ORBS;
 +                      else
 +                              return WEAPON_ELECTRO_SUICIDE_BOLT;
 +              }
 +              case WR_KILLMESSAGE:
 +              {
 +                      if(w_deathtype & HITTYPE_SECONDARY)
 +                      {
 +                              return WEAPON_ELECTRO_MURDER_ORBS;
 +                      }
 +                      else
 +                      {
 +                              if(w_deathtype & HITTYPE_BOUNCE)
 +                                      return WEAPON_ELECTRO_MURDER_COMBO;
 +                              else
 +                                      return WEAPON_ELECTRO_MURDER_BOLT;
 +                      }
 +              }
 +      }
 +      return TRUE;
 +}
 +#endif
 +#ifdef CSQC
 +float w_electro(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_IMPACTEFFECT:
 +              {
 +                      vector org2;
 +                      org2 = w_org + w_backoff * 6;
 +                      if(w_deathtype & HITTYPE_SECONDARY)
 +                      {
 +                              pointparticles(particleeffectnum("electro_ballexplode"), org2, '0 0 0', 1);
 +                              if(!w_issilent)
 +                                      sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTEN_NORM);
 +                      }
 +                      else
 +                      {
 +                              if(w_deathtype & HITTYPE_BOUNCE)
 +                              {
 +                                      // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls
 +                                      pointparticles(particleeffectnum("electro_combo"), org2, '0 0 0', 1);
 +                                      if(!w_issilent)
 +                                              sound(self, CH_SHOTS, "weapons/electro_impact_combo.wav", VOL_BASE, ATTEN_NORM);
 +                              }
 +                              else
 +                              {
 +                                      pointparticles(particleeffectnum("electro_impact"), org2, '0 0 0', 1);
 +                                      if(!w_issilent)
 +                                              sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTEN_NORM);
 +                              }
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_sound("weapons/electro_impact.wav");
 +                      precache_sound("weapons/electro_impact_combo.wav");
 +                      return TRUE;
 +              }
 +      }
 +      return TRUE;
 +}
 +#endif
 +#endif
index 83fe87ad2dfe066743d87ff91ce6ac2330e12d87,0000000000000000000000000000000000000000..466158441a96ba4ed37101df6950fa055785e8f5
mode 100644,000000..100644
--- /dev/null
@@@ -1,471 -1,0 +1,471 @@@
-               if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || IsDifferentTeam(e, self))
 +#ifdef REGISTER_WEAPON
 +REGISTER_WEAPON(
 +/* WEP_##id */ FIREBALL,
 +/* function */ w_fireball,
 +/* ammotype */ 0,
 +/* impulse  */ 9,
 +/* flags    */ WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH,
 +/* rating   */ BOT_PICKUP_RATING_MID,
 +/* model    */ "fireball",
 +/* netname  */ "fireball",
 +/* fullname */ _("Fireball")
 +);
 +#define FIREBALL_SETTINGS(weapon) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, animtime) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, refire) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, damage) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, damageforcescale) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, speed) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, lifetime) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, laserburntime) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, laserdamage) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, laseredgedamage) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, laserradius) \
 +      WEP_ADD_CVAR(weapon, MO_PRI,  edgedamage) \
 +      WEP_ADD_CVAR(weapon, MO_PRI,  force) \
 +      WEP_ADD_CVAR(weapon, MO_PRI,  radius) \
 +      WEP_ADD_CVAR(weapon, MO_PRI,  health) \
 +      WEP_ADD_CVAR(weapon, MO_PRI,  refire2) \
 +      WEP_ADD_CVAR(weapon, MO_PRI,  bfgdamage) \
 +      WEP_ADD_CVAR(weapon, MO_PRI,  bfgforce) \
 +      WEP_ADD_CVAR(weapon, MO_PRI,  bfgradius) \
 +      WEP_ADD_CVAR(weapon, MO_SEC,  damagetime) \
 +      WEP_ADD_CVAR(weapon, MO_SEC,  speed_up) \
 +      WEP_ADD_PROP(weapon, reloading_ammo, reload_ammo) \
 +      WEP_ADD_PROP(weapon, reloading_time, reload_time) \
 +      WEP_ADD_PROP(weapon, switchdelay_raise, switchdelay_raise) \
 +      WEP_ADD_PROP(weapon, switchdelay_drop, switchdelay_drop)
 +
 +#ifdef SVQC
 +FIREBALL_SETTINGS(fireball)
 +.float bot_primary_fireballmooth; // whatever a mooth is
 +.vector fireball_impactvec;
 +.float fireball_primarytime;
 +#endif
 +#else
 +#ifdef SVQC
 +void spawnfunc_weapon_fireball() { weapon_defaultspawnfunc(WEP_FIREBALL); }
 +
 +void W_Fireball_Explode (void)
 +{
 +      entity e;
 +      float dist;
 +      float points;
 +      vector dir;
 +      float d;
 +
 +      self.event_damage = func_null;
 +      self.takedamage = DAMAGE_NO;
 +
 +      // 1. dist damage
 +      d = (self.realowner.health + self.realowner.armorvalue);
 +      RadiusDamage (self, self.realowner, WEP_CVAR_PRI(fireball, damage), WEP_CVAR_PRI(fireball, edgedamage), WEP_CVAR_PRI(fireball, radius), world, world, WEP_CVAR_PRI(fireball, force), self.projectiledeathtype, other);
 +      if(self.realowner.health + self.realowner.armorvalue >= d)
 +      if(!self.cnt)
 +      {
 +              modeleffect_spawn("models/sphere/sphere.md3", 0, 0, self.origin, '0 0 0', '0 0 0', '0 0 0', 0, WEP_CVAR_PRI(fireball, bfgradius), 0.2, 0.05, 0.25);
 +
 +              // 2. bfg effect
 +              // NOTE: this cannot be made warpzone aware by design. So, better intentionally ignore warpzones here.
 +              for(e = findradius(self.origin, WEP_CVAR_PRI(fireball, bfgradius)); e; e = e.chain)
-       if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || IsDifferentTeam(e, self))
++              if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
 +              {
 +                      // can we see fireball?
 +                      traceline(e.origin + e.view_ofs, self.origin, MOVE_NORMAL, e);
 +                      if(/* trace_startsolid || */ trace_fraction != 1) // startsolid should be never happening anyway
 +                              continue;
 +                      // can we see player who shot fireball?
 +                      traceline(e.origin + e.view_ofs, self.realowner.origin + self.realowner.view_ofs, MOVE_NORMAL, e);
 +                      if(trace_ent != self.realowner)
 +                      if(/* trace_startsolid || */ trace_fraction != 1)
 +                              continue;
 +                      dist = vlen(self.origin - e.origin - e.view_ofs);
 +                      points = (1 - sqrt(dist / WEP_CVAR_PRI(fireball, bfgradius)));
 +                      if(points <= 0)
 +                              continue;
 +                      dir = normalize(e.origin + e.view_ofs - self.origin);
 +
 +                      if(accuracy_isgooddamage(self.realowner, e))
 +                              accuracy_add(self.realowner, WEP_FIREBALL, 0, WEP_CVAR_PRI(fireball, bfgdamage) * points);
 +
 +                      Damage(e, self, self.realowner, WEP_CVAR_PRI(fireball, bfgdamage) * points, self.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, e.origin + e.view_ofs, WEP_CVAR_PRI(fireball, bfgforce) * dir);
 +                      pointparticles(particleeffectnum("fireball_bfgdamage"), e.origin, -1 * dir, 1);
 +              }
 +      }
 +
 +      remove (self);
 +}
 +
 +void W_Fireball_TouchExplode (void)
 +{
 +      PROJECTILE_TOUCH;
 +      W_Fireball_Explode ();
 +}
 +
 +void W_Fireball_LaserPlay(float dt, 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 != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
 +      {
 +              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 W_Fireball_Think()
 +{
 +      if(time > self.pushltime)
 +      {
 +              self.cnt = 1;
 +              self.projectiledeathtype |= HITTYPE_SPLASH;
 +              W_Fireball_Explode();
 +              return;
 +      }
 +
 +      W_Fireball_LaserPlay(0.1, WEP_CVAR_PRI(fireball, laserradius), WEP_CVAR_PRI(fireball, laserdamage), WEP_CVAR_PRI(fireball, laseredgedamage), WEP_CVAR_PRI(fireball, laserburntime));
 +
 +      self.nextthink = time + 0.1;
 +}
 +
 +void W_Fireball_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 +{
 +      if(self.health <= 0)
 +              return;
 +              
 +      if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
 +              return; // g_projectiles_damage says to halt
 +              
 +      self.health = self.health - damage;
 +      if (self.health <= 0)
 +      {
 +              self.cnt = 1;
 +              W_PrepareExplosionByDamage(attacker, W_Fireball_Explode);
 +      }
 +}
 +
 +void W_Fireball_Attack1()
 +{
 +      entity proj;
 +
 +      W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 2, "weapons/fireball_fire2.wav", CH_WEAPON_A, WEP_CVAR_PRI(fireball, damage) + WEP_CVAR_PRI(fireball, bfgdamage));
 +
 +      pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
 +
 +      proj = spawn ();
 +      proj.classname = "plasma_prim";
 +      proj.owner = proj.realowner = self;
 +      proj.bot_dodge = TRUE;
 +      proj.bot_dodgerating = WEP_CVAR_PRI(fireball, damage);
 +      proj.pushltime = time + WEP_CVAR_PRI(fireball, lifetime);
 +      proj.use = W_Fireball_Explode;
 +      proj.think = W_Fireball_Think;
 +      proj.nextthink = time;
 +      proj.health = WEP_CVAR_PRI(fireball, health);
 +      proj.team = self.team;
 +      proj.event_damage = W_Fireball_Damage;
 +      proj.takedamage = DAMAGE_YES;
 +      proj.damageforcescale = WEP_CVAR_PRI(fireball, damageforcescale);
 +      PROJECTILE_MAKETRIGGER(proj);
 +      proj.projectiledeathtype = WEP_FIREBALL;
 +      setorigin(proj, w_shotorg);
 +
 +      proj.movetype = MOVETYPE_FLY;
 +      W_SETUPPROJECTILEVELOCITY(proj, g_balance_fireball_primary);
 +      proj.angles = vectoangles(proj.velocity);
 +      proj.touch = W_Fireball_TouchExplode;
 +      setsize(proj, '-16 -16 -16', '16 16 16');
 +      proj.flags = FL_PROJECTILE;
 +    proj.missile_flags = MIF_SPLASH | MIF_PROXY;
 +    
 +      CSQCProjectile(proj, TRUE, PROJECTILE_FIREBALL, TRUE);
 +
 +      other = proj; MUTATOR_CALLHOOK(EditProjectile);
 +}
 +
 +void W_Fireball_AttackEffect(float i, vector f_diff)
 +{
 +      W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 0, "", 0, 0);
 +      w_shotorg += f_diff_x * v_up + f_diff_y * v_right;
 +      pointparticles(particleeffectnum("fireball_preattack_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
 +}
 +
 +void W_Fireball_Attack1_Frame4()
 +{
 +      W_Fireball_Attack1();
 +      weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), w_ready);
 +}
 +
 +void W_Fireball_Attack1_Frame3()
 +{
 +      W_Fireball_AttackEffect(0, '+1.25 +3.75 0');
 +      weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame4);
 +}
 +
 +void W_Fireball_Attack1_Frame2()
 +{
 +      W_Fireball_AttackEffect(0, '-1.25 +3.75 0');
 +      weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame3);
 +}
 +
 +void W_Fireball_Attack1_Frame1()
 +{
 +      W_Fireball_AttackEffect(1, '+1.25 -3.75 0');
 +      weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame2);
 +}
 +
 +void W_Fireball_Attack1_Frame0()
 +{
 +      W_Fireball_AttackEffect(0, '-1.25 -3.75 0');
 +      sound (self, CH_WEAPON_SINGLE, "weapons/fireball_prefire2.wav", VOL_BASE, ATTEN_NORM);
 +      weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame1);
 +}
 +
 +void W_Firemine_Think()
 +{
 +      if(time > self.pushltime)
 +      {
 +              remove(self);
 +              return;
 +      }
 +
 +      // make it "hot" once it leaves its owner
 +      if(self.owner)
 +      {
 +              if(vlen(self.origin - self.owner.origin - self.owner.view_ofs) > WEP_CVAR_SEC(fireball, laserradius))
 +              {
 +                      self.cnt += 1;
 +                      if(self.cnt == 3)
 +                              self.owner = world;
 +              }
 +              else
 +                      self.cnt = 0;
 +      }
 +
 +      W_Fireball_LaserPlay(0.1, WEP_CVAR_SEC(fireball, laserradius), WEP_CVAR_SEC(fireball, laserdamage), WEP_CVAR_SEC(fireball, laseredgedamage), WEP_CVAR_SEC(fireball, laserburntime));
 +
 +      self.nextthink = time + 0.1;
 +}
 +
 +void W_Firemine_Touch (void)
 +{
 +      PROJECTILE_TOUCH;
 +      if (other.takedamage == DAMAGE_AIM)
 +      if(Fire_AddDamage(other, self.realowner, WEP_CVAR_SEC(fireball, damage), WEP_CVAR_SEC(fireball, damagetime), self.projectiledeathtype) >= 0)
 +      {
 +              remove(self);
 +              return;
 +      }
 +      self.projectiledeathtype |= HITTYPE_BOUNCE;
 +}
 +
 +void W_Fireball_Attack2()
 +{
 +      entity proj;
 +      vector f_diff;
 +      float c;
 +
 +      c = mod(self.bulletcounter, 4);
 +      switch(c)
 +      {
 +              case 0:
 +                      f_diff = '-1.25 -3.75 0';
 +                      break;
 +              case 1:
 +                      f_diff = '+1.25 -3.75 0';
 +                      break;
 +              case 2:
 +                      f_diff = '-1.25 +3.75 0';
 +                      break;
 +              case 3:
 +              default:
 +                      f_diff = '+1.25 +3.75 0';
 +                      break;
 +      }
 +      W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 2, "weapons/fireball_fire.wav", CH_WEAPON_A, WEP_CVAR_SEC(fireball, damage));
 +      traceline(w_shotorg, w_shotorg + f_diff_x * v_up + f_diff_y * v_right, MOVE_NORMAL, self);
 +      w_shotorg = trace_endpos;
 +
 +      pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
 +
 +      proj = spawn ();
 +      proj.owner = proj.realowner = self;
 +      proj.classname = "grenade";
 +      proj.bot_dodge = TRUE;
 +      proj.bot_dodgerating = WEP_CVAR_SEC(fireball, damage);
 +      proj.movetype = MOVETYPE_BOUNCE;
 +      proj.projectiledeathtype = WEP_FIREBALL | HITTYPE_SECONDARY;
 +      proj.touch = W_Firemine_Touch;
 +      PROJECTILE_MAKETRIGGER(proj);
 +      setsize(proj, '-4 -4 -4', '4 4 4');
 +      setorigin(proj, w_shotorg);
 +      proj.think = W_Firemine_Think;
 +      proj.nextthink = time;
 +      proj.damageforcescale = WEP_CVAR_SEC(fireball, damageforcescale);
 +      proj.pushltime = time + WEP_CVAR_SEC(fireball, lifetime);
 +      W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_fireball_secondary);
 +
 +      proj.angles = vectoangles(proj.velocity);
 +      proj.flags = FL_PROJECTILE;
 +    proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC;
 +    
 +      CSQCProjectile(proj, TRUE, PROJECTILE_FIREMINE, TRUE);
 +
 +      other = proj; MUTATOR_CALLHOOK(EditProjectile);
 +}
 +
 +float w_fireball(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_AIM:
 +              {
 +                      self.BUTTON_ATCK = FALSE;
 +                      self.BUTTON_ATCK2 = FALSE;
 +                      if (self.bot_primary_fireballmooth == 0)
 +                      {
 +                              if(bot_aim(WEP_CVAR_PRI(fireball, speed), 0, WEP_CVAR_PRI(fireball, lifetime), FALSE))
 +                              {
 +                                      self.BUTTON_ATCK = TRUE;
 +                                      if(random() < 0.02) self.bot_primary_fireballmooth = 0;
 +                              }
 +                      }
 +                      else
 +                      {
 +                              if(bot_aim(WEP_CVAR_SEC(fireball, speed), WEP_CVAR_SEC(fireball, speed_up), WEP_CVAR_SEC(fireball, lifetime), TRUE))
 +                              {
 +                                      self.BUTTON_ATCK2 = TRUE;
 +                                      if(random() < 0.01) self.bot_primary_fireballmooth = 1;
 +                              }
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_THINK:
 +              {
 +                      if (self.BUTTON_ATCK)
 +                      {
 +                              if (time >= self.fireball_primarytime)
 +                              if (weapon_prepareattack(0, WEP_CVAR_PRI(fireball, refire)))
 +                              {
 +                                      W_Fireball_Attack1_Frame0();
 +                                      self.fireball_primarytime = time + WEP_CVAR_PRI(fireball, refire2) * W_WeaponRateFactor();
 +                              }
 +                      }
 +                      else if (self.BUTTON_ATCK2)
 +                      {
 +                              if (weapon_prepareattack(1, WEP_CVAR_SEC(fireball, refire)))
 +                              {
 +                                      W_Fireball_Attack2();
 +                                      weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(fireball, animtime), w_ready);
 +                              }
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_model ("models/weapons/g_fireball.md3");
 +                      precache_model ("models/weapons/v_fireball.md3");
 +                      precache_model ("models/weapons/h_fireball.iqm");
 +                      precache_model ("models/sphere/sphere.md3");
 +                      precache_sound ("weapons/fireball_fire.wav");
 +                      precache_sound ("weapons/fireball_fire2.wav");
 +                      precache_sound ("weapons/fireball_prefire2.wav");
 +                      WEP_SET_PROPS(FIREBALL_SETTINGS(fireball), WEP_FIREBALL)
 +                      return TRUE;
 +              }
 +              case WR_SETUP:
 +              {
 +                      self.current_ammo = ammo_none;
 +                      return TRUE;
 +              }
 +              case WR_CHECKAMMO1:
 +              case WR_CHECKAMMO2:
 +              {
 +                      return TRUE; // fireball has infinite ammo
 +              }
 +              case WR_CONFIG:
 +              {
 +                      WEP_CONFIG_SETTINGS(FIREBALL_SETTINGS(fireball))
 +                      return TRUE;
 +              }
 +              case WR_RESETPLAYER:
 +              {
 +                      self.fireball_primarytime = time;
 +                      return TRUE;
 +              }
 +              case WR_SUICIDEMESSAGE:
 +              {
 +                      if(w_deathtype & HITTYPE_SECONDARY)
 +                              return WEAPON_FIREBALL_SUICIDE_FIREMINE;
 +                      else
 +                              return WEAPON_FIREBALL_SUICIDE_BLAST;
 +              }
 +              case WR_KILLMESSAGE:
 +              {
 +                      if(w_deathtype & HITTYPE_SECONDARY)
 +                              return WEAPON_FIREBALL_MURDER_FIREMINE;
 +                      else
 +                              return WEAPON_FIREBALL_MURDER_BLAST;
 +              }
 +      }
 +      return TRUE;
 +}
 +#endif
 +#ifdef CSQC
 +float w_fireball(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_IMPACTEFFECT:
 +              {
 +                      vector org2;
 +                      if(w_deathtype & HITTYPE_SECONDARY)
 +                      {
 +                              // firemine goes out silently
 +                      }
 +                      else
 +                      {
 +                              org2 = w_org + w_backoff * 16;
 +                              pointparticles(particleeffectnum("fireball_explode"), org2, '0 0 0', 1);
 +                              if(!w_issilent)
 +                                      sound(self, CH_SHOTS, "weapons/fireball_impact2.wav", VOL_BASE, ATTEN_NORM * 0.25); // long range boom
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_sound("weapons/fireball_impact2.wav");
 +                      return TRUE;
 +              }
 +      }
 +
 +      return TRUE;
 +}
 +#endif
 +#endif
index 06ff4ee0a9cd7f4e84b00a3f5d4b9c2fec5ec0d5,0000000000000000000000000000000000000000..8322abeb4d4c84fdbf8a1c3818c5cc6f6100ab93
mode 100644,000000..100644
--- /dev/null
@@@ -1,611 -1,0 +1,611 @@@
-                       if(IsDifferentTeam(self.realowner, other))
 +#ifdef REGISTER_WEAPON
 +REGISTER_WEAPON(
 +/* WEP_##id */ MINE_LAYER,
 +/* function */ w_minelayer,
 +/* ammotype */ IT_ROCKETS,
 +/* impulse  */ 4,
 +/* flags    */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
 +/* rating   */ BOT_PICKUP_RATING_HIGH,
 +/* model    */ "minelayer",
 +/* netname  */ "minelayer",
 +/* fullname */ _("Mine Layer")
 +);
 +
 +#define MINELAYER_SETTINGS(weapon) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, ammo) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, animtime) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, damage) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, damageforcescale) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, detonatedelay) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, edgedamage) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, force) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, health) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, lifetime) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, lifetime_countdown) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, limit) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, protection) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, proximityradius) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, radius) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, refire) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, remote_damage) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, remote_edgedamage) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, remote_force) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, remote_radius) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, speed) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, time) \
 +      WEP_ADD_PROP(weapon, reloading_ammo, reload_ammo) \
 +      WEP_ADD_PROP(weapon, reloading_time, reload_time) \
 +      WEP_ADD_PROP(weapon, switchdelay_raise, switchdelay_raise) \
 +      WEP_ADD_PROP(weapon, switchdelay_drop, switchdelay_drop)
 +
 +#ifdef SVQC
 +MINELAYER_SETTINGS(minelayer)
 +void W_Mine_Think (void);
 +.float minelayer_detonate, mine_explodeanyway;
 +.float mine_time;
 +.vector mine_orientation;
 +#endif
 +#else
 +#ifdef SVQC
 +void spawnfunc_weapon_minelayer() { weapon_defaultspawnfunc(WEP_MINE_LAYER); }
 +
 +void W_Mine_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_Mine_Think;
 +      newmine.nextthink = time;
 +      newmine.cnt = self.cnt;
 +      newmine.flags = self.flags;
 +
 +      remove(self);
 +      self = newmine;
 +
 +      if(to)
 +              SetMovetypeFollow(self, to);
 +}
 +
 +void W_Mine_Explode ()
 +{
 +      if(other.takedamage == DAMAGE_AIM)
 +              if(IS_PLAYER(other))
-                       if(head == self.realowner || !IsDifferentTeam(head, self.realowner))
++                      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_Mine_DoRemoteExplode ()
 +{
 +      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_Mine_RemoteExplode ()
 +{
 +      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_Mine_DoRemoteExplode();
 +              }
 +}
 +
 +void W_Mine_ProximityExplode ()
 +{
 +      // 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 && IsDifferentTeam(head, self.realowner)) // don't trigger for team mates
++                      if(head == self.realowner || SAME_TEAM(head, self.realowner))
 +                              return;
 +                      head = head.chain;
 +              }
 +      }
 +
 +      self.mine_time = 0;
 +      W_Mine_Explode();
 +}
 +
 +float W_Mine_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_Mine_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(self.realowner) || self.realowner.deadflag != DEAD_NO)
 +      {
 +              other = world;
 +              self.projectiledeathtype |= HITTYPE_BOUNCE;
 +              W_Mine_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)
++              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_Mine_ProximityExplode();
 +              return;
 +      }
 +
 +      // remote detonation
 +      if (self.realowner.weapon == WEP_MINE_LAYER)
 +      if (self.realowner.deadflag == DEAD_NO)
 +      if (self.minelayer_detonate)
 +              W_Mine_RemoteExplode();
 +}
 +
 +void W_Mine_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_Mine_Stick(other);
 +      }
 +}
 +
 +void W_Mine_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_Mine_Explode);
 +}
 +
 +void W_Mine_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(ammo_rockets, WEP_CVAR(minelayer, ammo), autocvar_g_balance_minelayer_reload_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_Mine_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_SetupProjectileVelocity(mine, WEP_CVAR(minelayer, speed), 0);
 +      mine.angles = vectoangles (mine.velocity);
 +
 +      mine.touch = W_Mine_Touch;
 +      mine.think = W_Mine_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_Mine_Count(self);
 +}
 +
 +float W_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 not (W_PlacedMines(FALSE) && self.ammo_rockets < WEP_CVAR(minelayer, ammo))
 +                                      WEP_ACTION(self.weapon, WR_RELOAD);
 +                      }
 +                      else if (self.BUTTON_ATCK)
 +                      {
 +                              if(weapon_prepareattack(0, WEP_CVAR(minelayer, refire)))
 +                              {
 +                                      W_Mine_Attack();
 +                                      weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(minelayer, animtime), w_ready);
 +                              }
 +                      }
 +
 +                      if (self.BUTTON_ATCK2)
 +                      {
 +                              if(W_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");
 +                      WEP_SET_PROPS(MINELAYER_SETTINGS(minelayer), WEP_MINE_LAYER)
 +                      return TRUE;
 +              }
 +              case WR_SETUP:
 +              {
 +                      self.current_ammo = ammo_rockets;
 +                      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.ammo_rockets >= 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_PlacedMines(FALSE))
 +                              return TRUE;
 +                      else
 +                              return FALSE;
 +              }
 +              case WR_CONFIG:
 +              {
 +                      WEP_CONFIG_SETTINGS(MINELAYER_SETTINGS(minelayer))
 +                      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 TRUE;
 +}
 +#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;
 +              }
 +      }
 +      return TRUE;
 +}
 +#endif
 +#endif
index 712944ec0c1630ae4a6ce613efda91cd56f0ea47,0000000000000000000000000000000000000000..b6f156dfc38b019af8960ecb343eeb6485df5e3a
mode 100644,000000..100644
--- /dev/null
@@@ -1,477 -1,0 +1,477 @@@
-                       if(IsDifferentTeam(self.realowner, other))
 +#ifdef REGISTER_WEAPON
 +REGISTER_WEAPON(
 +/* WEP_##id */ GRENADE_LAUNCHER,
 +/* function */ w_glauncher,
 +/* ammotype */ IT_ROCKETS,
 +/* impulse  */ 4,
 +/* flags    */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
 +/* rating   */ BOT_PICKUP_RATING_MID,
 +/* model    */ "gl",
 +/* netname  */ "grenadelauncher",
 +/* fullname */ _("Mortar")
 +);
 +
 +#define MORTAR_SETTINGS(weapon) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, ammo) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, animtime) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, bouncefactor) \
 +      WEP_ADD_CVAR(weapon, MO_NONE, bouncestop) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, damage) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, damageforcescale) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, edgedamage) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, force) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, health) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, lifetime) \
 +      WEP_ADD_CVAR(weapon, MO_SEC,  lifetime_bounce) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, lifetime_stick) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, radius) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, refire) \
 +      WEP_ADD_CVAR(weapon, MO_PRI,  remote_minbouncecnt) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, speed) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, speed_up) \
 +      WEP_ADD_CVAR(weapon, MO_BOTH, type) \
 +      WEP_ADD_PROP(weapon, reloading_ammo, reload_ammo) \
 +      WEP_ADD_PROP(weapon, reloading_time, reload_time) \
 +      WEP_ADD_PROP(weapon, switchdelay_raise, switchdelay_raise) \
 +      WEP_ADD_PROP(weapon, switchdelay_drop, switchdelay_drop)
 +
 +#ifdef SVQC
 +MORTAR_SETTINGS(mortar)
 +.float gl_detonate_later;
 +.float gl_bouncecnt;
 +#endif
 +#else
 +#ifdef SVQC
 +
 +void W_Grenade_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;
 +
 +      if(self.movetype == MOVETYPE_NONE)
 +              self.velocity = self.oldvelocity;
 +
 +      RadiusDamage (self, self.realowner, WEP_CVAR_PRI(mortar, damage), WEP_CVAR_PRI(mortar, edgedamage), WEP_CVAR_PRI(mortar, radius), world, world, WEP_CVAR_PRI(mortar, force), self.projectiledeathtype, other);
 +
 +      remove (self);
 +}
 +
 +void W_Grenade_Explode2 (void)
 +{
 +      if(other.takedamage == DAMAGE_AIM)
 +              if(IS_PLAYER(other))
 +                      if(IsDifferentTeam(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;
 +
 +      if(self.movetype == MOVETYPE_NONE)
 +              self.velocity = self.oldvelocity;
 +
 +      RadiusDamage (self, self.realowner, WEP_CVAR_SEC(mortar, damage), WEP_CVAR_SEC(mortar, edgedamage), WEP_CVAR_SEC(mortar, radius), world, world, WEP_CVAR_SEC(mortar, force), self.projectiledeathtype, other);
 +
 +      remove (self);
 +}
 +
 +void W_Grenade_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 +{
 +      if (self.health <= 0)
 +              return;
 +              
 +      if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
 +              return; // g_projectiles_damage says to halt
 +              
 +      self.health = self.health - damage;
 +      
 +      if (self.health <= 0)
 +              W_PrepareExplosionByDamage(attacker, self.use);
 +}
 +
 +void W_Grenade_Think1 (void)
 +{
 +      self.nextthink = time;
 +      if (time > self.cnt)
 +      {
 +              other = world;
 +              self.projectiledeathtype |= HITTYPE_BOUNCE;
 +              W_Grenade_Explode ();
 +              return;
 +      }
 +      if(self.gl_detonate_later && self.gl_bouncecnt >= WEP_CVAR_PRI(mortar, remote_minbouncecnt))
 +              W_Grenade_Explode();
 +}
 +
 +void W_Grenade_Touch1 (void)
 +{
 +      PROJECTILE_TOUCH;
 +      if (other.takedamage == DAMAGE_AIM || WEP_CVAR_PRI(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile
 +      {
 +              self.use ();
 +      }
 +      else if (WEP_CVAR_PRI(mortar, type) == 1) // bounce
 +      {
 +              float r;
 +              r = random() * 6;
 +              if(r < 1)
 +                      spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM);
 +              else if(r < 2)
 +                      spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM);
 +              else if(r < 3)
 +                      spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM);
 +              else if(r < 4)
 +                      spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM);
 +              else if(r < 5)
 +                      spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM);
 +              else
 +                      spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM);
 +              self.projectiledeathtype |= HITTYPE_BOUNCE;
 +              self.gl_bouncecnt += 1;
 +      }
 +      else if(WEP_CVAR_PRI(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
 +      {
 +              spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM);
 +
 +              // let it stick whereever it is
 +              self.oldvelocity = self.velocity;
 +              self.velocity = '0 0 0';
 +              self.movetype = MOVETYPE_NONE; // also disables gravity
 +              self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
 +              UpdateCSQCProjectile(self);
 +
 +              // do not respond to any more touches
 +              self.solid = SOLID_NOT;
 +
 +              self.nextthink = min(self.nextthink, time + WEP_CVAR_PRI(mortar, lifetime_stick));
 +      }
 +}
 +
 +void W_Grenade_Touch2 (void)
 +{
 +      PROJECTILE_TOUCH;
 +      if (other.takedamage == DAMAGE_AIM || WEP_CVAR_SEC(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile
 +      {
 +              self.use ();
 +      }
 +      else if (WEP_CVAR_SEC(mortar, type) == 1) // bounce
 +      {
 +              float r;
 +              r = random() * 6;
 +              if(r < 1)
 +                      spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM);
 +              else if(r < 2)
 +                      spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM);
 +              else if(r < 3)
 +                      spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM);
 +              else if(r < 4)
 +                      spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM);
 +              else if(r < 5)
 +                      spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM);
 +              else
 +                      spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM);
 +              self.projectiledeathtype |= HITTYPE_BOUNCE;
 +              self.gl_bouncecnt += 1;
 +              
 +              if (WEP_CVAR_SEC(mortar, lifetime_bounce) && self.gl_bouncecnt == 1)
 +                      self.nextthink = time + WEP_CVAR_SEC(mortar, lifetime_bounce);
 +                      
 +      }
 +      else if(WEP_CVAR_SEC(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
 +      {
 +              spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM);
 +
 +              // let it stick whereever it is
 +              self.oldvelocity = self.velocity;
 +              self.velocity = '0 0 0';
 +              self.movetype = MOVETYPE_NONE; // also disables gravity
 +              self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
 +              UpdateCSQCProjectile(self);
 +
 +              // do not respond to any more touches
 +              self.solid = SOLID_NOT;
 +
 +              self.nextthink = min(self.nextthink, time + WEP_CVAR_SEC(mortar, lifetime_stick));
 +      }
 +}
 +
 +void W_Grenade_Attack (void)
 +{
 +      entity gren;
 +
 +      W_DecreaseAmmo(ammo_rockets, WEP_CVAR_PRI(mortar, ammo), autocvar_g_balance_mortar_reload_ammo); // WEAPONTODO
 +
 +      W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, WEP_CVAR_PRI(mortar, damage));
 +      w_shotdir = v_forward; // no TrueAim for grenades please
 +
 +      pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
 +
 +      gren = spawn ();
 +      gren.owner = gren.realowner = self;
 +      gren.classname = "grenade";
 +      gren.bot_dodge = TRUE;
 +      gren.bot_dodgerating = WEP_CVAR_PRI(mortar, damage);
 +      gren.movetype = MOVETYPE_BOUNCE;
 +      gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
 +      gren.bouncestop = WEP_CVAR(mortar, bouncestop);
 +      PROJECTILE_MAKETRIGGER(gren);
 +      gren.projectiledeathtype = WEP_GRENADE_LAUNCHER;
 +      setorigin(gren, w_shotorg);
 +      setsize(gren, '-3 -3 -3', '3 3 3');
 +
 +      gren.cnt = time + WEP_CVAR_PRI(mortar, lifetime);
 +      gren.nextthink = time;
 +      gren.think = W_Grenade_Think1;
 +      gren.use = W_Grenade_Explode;
 +      gren.touch = W_Grenade_Touch1;
 +
 +      gren.takedamage = DAMAGE_YES;
 +      gren.health = WEP_CVAR_PRI(mortar, health);
 +      gren.damageforcescale = WEP_CVAR_PRI(mortar, damageforcescale);
 +      gren.event_damage = W_Grenade_Damage;
 +      gren.damagedbycontents = TRUE;
 +      gren.missile_flags = MIF_SPLASH | MIF_ARC;
 +      W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_mortar_primary); // WEAPONTODO
 +
 +      gren.angles = vectoangles (gren.velocity);
 +      gren.flags = FL_PROJECTILE;
 +
 +      if(WEP_CVAR_PRI(mortar, type) == 0 || WEP_CVAR_PRI(mortar, type) == 2)
 +              CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
 +      else
 +              CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE);
 +
 +      other = gren; MUTATOR_CALLHOOK(EditProjectile);
 +}
 +
 +void W_Grenade_Attack2 (void)
 +{
 +      entity gren;
 +
 +      W_DecreaseAmmo(ammo_rockets, WEP_CVAR_SEC(mortar, ammo), autocvar_g_balance_mortar_reload_ammo);
 +
 +      W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, WEP_CVAR_SEC(mortar, damage));
 +      w_shotdir = v_forward; // no TrueAim for grenades please
 +
 +      pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
 +
 +      gren = spawn ();
 +      gren.owner = gren.realowner = self;
 +      gren.classname = "grenade";
 +      gren.bot_dodge = TRUE;
 +      gren.bot_dodgerating = WEP_CVAR_SEC(mortar, damage);
 +      gren.movetype = MOVETYPE_BOUNCE;
 +      gren.bouncefactor = WEP_CVAR(mortar, bouncefactor);
 +      gren.bouncestop = WEP_CVAR(mortar, bouncestop);
 +      PROJECTILE_MAKETRIGGER(gren);
 +      gren.projectiledeathtype = WEP_GRENADE_LAUNCHER | HITTYPE_SECONDARY;
 +      setorigin(gren, w_shotorg);
 +      setsize(gren, '-3 -3 -3', '3 3 3');
 +
 +      gren.nextthink = time + WEP_CVAR_SEC(mortar, lifetime);
 +      gren.think = adaptor_think2use_hittype_splash;
 +      gren.use = W_Grenade_Explode2;
 +      gren.touch = W_Grenade_Touch2;
 +
 +      gren.takedamage = DAMAGE_YES;
 +      gren.health = WEP_CVAR_SEC(mortar, health);
 +      gren.damageforcescale = WEP_CVAR_SEC(mortar, damageforcescale);
 +      gren.event_damage = W_Grenade_Damage;
 +      gren.damagedbycontents = TRUE;
 +      gren.missile_flags = MIF_SPLASH | MIF_ARC;
 +      W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_mortar_secondary); // WEAPONTODO
 +
 +      gren.angles = vectoangles (gren.velocity);
 +      gren.flags = FL_PROJECTILE;
 +
 +      if(WEP_CVAR_SEC(mortar, type) == 0 || WEP_CVAR_SEC(mortar, type) == 2)
 +              CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
 +      else
 +              CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE);
 +
 +      other = gren; MUTATOR_CALLHOOK(EditProjectile);
 +}
 +
 +void spawnfunc_weapon_grenadelauncher (void)
 +{
 +      weapon_defaultspawnfunc(WEP_GRENADE_LAUNCHER);
 +}
 +
 +.float bot_secondary_grenademooth;
 +float w_glauncher(float req)
 +{
 +      entity nade;
 +      float nadefound;
 +      float ammo_amount;
 +      switch(req)
 +      {
 +              case WR_AIM:
 +              {
 +                      self.BUTTON_ATCK = FALSE;
 +                      self.BUTTON_ATCK2 = FALSE;
 +                      if (self.bot_secondary_grenademooth == 0) // WEAPONTODO: merge this into using WEP_CVAR_BOTH
 +                      {
 +                              if(bot_aim(WEP_CVAR_PRI(mortar, speed), WEP_CVAR_PRI(mortar, speed_up), WEP_CVAR_PRI(mortar, lifetime), TRUE))
 +                              {
 +                                      self.BUTTON_ATCK = TRUE;
 +                                      if(random() < 0.01) self.bot_secondary_grenademooth = 1;
 +                              }
 +                      }
 +                      else
 +                      {
 +                              if(bot_aim(WEP_CVAR_SEC(mortar, speed), WEP_CVAR_SEC(mortar, speed_up), WEP_CVAR_SEC(mortar, lifetime), TRUE))
 +                              {
 +                                      self.BUTTON_ATCK2 = TRUE;
 +                                      if(random() < 0.02) self.bot_secondary_grenademooth = 0;
 +                              }
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              /*case WR_CALCINFO:
 +              {
 +                      wepinfo_pri_refire = max3(sys_frametime, WEP_CVAR_PRI(mortar, refire), WEP_CVAR_PRI(mortar, animtime));
 +                      wepinfo_pri_dps = (WEP_CVAR_PRI(mortar, damage) * (1 / wepinfo_pri_refire));
 +                      wepinfo_pri_speed = (1 / max(1, (10000 / max(1, WEP_CVAR_PRI(mortar, speed)))));
 +
 +                      // for the range calculation, closer to 1 is better
 +                      wepinfo_pri_range_max = 2000 * wepinfo_pri_speed;
 +                      wepinfo_pri_range = wepinfo_pri_speed * WEP_CVAR_PRI(mortar, 
 +                      
 +                      wepinfo_sec_refire = max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime));
 +                      wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / wepinfo_sec_refire));
 +                      
 +                      wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime))));
 +                      wepinfo_ter_dps = 0;
 +                      */
 +              case WR_THINK:
 +              {
 +                      if(autocvar_g_balance_mortar_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo))) // forced reload
 +                              WEP_ACTION(self.weapon, WR_RELOAD);
 +                      else if (self.BUTTON_ATCK)
 +                      {
 +                              if (weapon_prepareattack(0, WEP_CVAR_PRI(mortar, refire)))
 +                              {
 +                                      W_Grenade_Attack();
 +                                      weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(mortar, animtime), w_ready);
 +                              }
 +                      }
 +                      else if (self.BUTTON_ATCK2)
 +                      {
 +                              if (cvar("g_balance_mortar_secondary_remote_detonateprimary"))
 +                              {
 +                                      nadefound = 0;
 +                                      for(nade = world; (nade = find(nade, classname, "grenade")); ) if(nade.realowner == self)
 +                                      {
 +                                              if(!nade.gl_detonate_later)
 +                                              {
 +                                                      nade.gl_detonate_later = TRUE;
 +                                                      nadefound = 1;
 +                                              }
 +                                      }
 +                                      if(nadefound)
 +                                              sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM);
 +                              }
 +                              else if (weapon_prepareattack(1, WEP_CVAR_SEC(mortar, refire)))
 +                              {
 +                                      W_Grenade_Attack2();
 +                                      weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(mortar, animtime), w_ready);
 +                              }
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_model ("models/weapons/g_gl.md3");
 +                      precache_model ("models/weapons/v_gl.md3");
 +                      precache_model ("models/weapons/h_gl.iqm");
 +                      precache_sound ("weapons/grenade_bounce1.wav");
 +                      precache_sound ("weapons/grenade_bounce2.wav");
 +                      precache_sound ("weapons/grenade_bounce3.wav");
 +                      precache_sound ("weapons/grenade_bounce4.wav");
 +                      precache_sound ("weapons/grenade_bounce5.wav");
 +                      precache_sound ("weapons/grenade_bounce6.wav");
 +                      precache_sound ("weapons/grenade_stick.wav");
 +                      precache_sound ("weapons/grenade_fire.wav");
 +                      WEP_SET_PROPS(MORTAR_SETTINGS(mortar), WEP_GRENADE_LAUNCHER)
 +                      return TRUE;
 +              }
 +              case WR_SETUP:
 +              {
 +                      self.current_ammo = ammo_rockets;
 +                      return TRUE;
 +              }
 +              case WR_CHECKAMMO1:
 +              {
 +                      ammo_amount = self.ammo_rockets >= WEP_CVAR_PRI(mortar, ammo);
 +                      ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= WEP_CVAR_PRI(mortar, ammo);
 +                      return ammo_amount;
 +              }
 +              case WR_CHECKAMMO2:
 +              {
 +                      ammo_amount = self.ammo_rockets >= WEP_CVAR_SEC(mortar, ammo);
 +                      ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= WEP_CVAR_SEC(mortar, ammo);
 +                      return ammo_amount;
 +              }
 +              case WR_CONFIG:
 +              {
 +                      WEP_CONFIG_SETTINGS(MORTAR_SETTINGS(mortar))
 +                      return TRUE;
 +              }
 +              case WR_RELOAD:
 +              {
 +                      W_Reload(min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo)), "weapons/reload.wav"); // WEAPONTODO
 +                      return TRUE;
 +              }
 +              case WR_SUICIDEMESSAGE:
 +              {
 +                      if(w_deathtype & HITTYPE_SECONDARY)
 +                              return WEAPON_MORTAR_SUICIDE_BOUNCE;
 +                      else
 +                              return WEAPON_MORTAR_SUICIDE_EXPLODE;
 +              }
 +              case WR_KILLMESSAGE:
 +              {
 +                      if(w_deathtype & HITTYPE_SECONDARY)
 +                              return WEAPON_MORTAR_MURDER_BOUNCE;
 +                      else
 +                              return WEAPON_MORTAR_MURDER_EXPLODE;
 +              }
 +      }
 +      return TRUE;
 +}
 +#endif
 +#ifdef CSQC
 +float w_glauncher(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_IMPACTEFFECT:
 +              {
 +                      vector org2;
 +                      org2 = w_org + w_backoff * 12;
 +                      pointparticles(particleeffectnum("grenade_explode"), org2, '0 0 0', 1);
 +                      if(!w_issilent)
 +                              sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
 +                              
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_sound("weapons/grenade_impact.wav");
 +                      return TRUE;
 +              }
 +      }
 +      return TRUE;
 +}
 +#endif
 +#endif
Simple merge
Simple merge
Simple merge
index 2429b2cb57ade36c0fe89f8581d38091aea9f857,0000000000000000000000000000000000000000..084a9d0e4d7b121878f851bb06ecda17cf9d1795
mode 100644,000000..100644
--- /dev/null
@@@ -1,120 -1,0 +1,120 @@@
-       if(IsDifferentTeam(attacker, targ))
 +float accuracy_byte(float n, float d)
 +{
 +      //print(sprintf("accuracy: %d / %d\n", n, d));
 +      if(n <= 0)
 +              return 0;
 +      if(n > d)
 +              return 255;
 +      return 1 + rint(n * 100.0 / d);
 +}
 +
 +float accuracy_send(entity to, float sf)
 +{
 +      float w, f;
 +      entity a;
 +      WriteByte(MSG_ENTITY, ENT_CLIENT_ACCURACY);
 +
 +      a = self.owner;
 +      if(IS_SPEC(a))
 +              a = a.enemy;
 +      a = a.accuracy;
 +
 +      if(to != a.owner)
 +              if not(self.owner.cvar_cl_accuracy_data_share && autocvar_sv_accuracy_data_share)
 +                      sf = 0;
 +      // note: zero sendflags can never be sent... so we can use that to say that we send no accuracy!
 +      WriteInt24_t(MSG_ENTITY, sf);
 +      if(sf == 0)
 +              return TRUE;
 +      // note: we know that client and server agree about SendFlags...
 +      for(w = 0, f = 1; w <= WEP_LAST - WEP_FIRST; ++w)
 +      {
 +              if(sf & f)
 +                      WriteByte(MSG_ENTITY, accuracy_byte(self.(accuracy_hit[w]), self.(accuracy_fired[w])));
 +              if(f == 0x800000)
 +                      f = 1;
 +              else
 +                      f *= 2;
 +      }
 +      return TRUE;
 +}
 +
 +// init/free
 +void accuracy_init(entity e)
 +{
 +      e.accuracy = spawn();
 +      e.accuracy.owner = e;
 +      e.accuracy.classname = "accuracy";
 +      e.accuracy.drawonlytoclient = e;
 +      Net_LinkEntity(e.accuracy, FALSE, 0, accuracy_send);
 +}
 +
 +void accuracy_free(entity e)
 +{
 +      remove(e.accuracy);
 +}
 +
 +// force a resend of a player's accuracy stats
 +void accuracy_resend(entity e)
 +{
 +      e.accuracy.SendFlags = 0xFFFFFF;
 +}
 +
 +// update accuracy stats
 +.float hit_time;
 +.float fired_time;
 +
 +void accuracy_add(entity e, float w, float fired, float hit)
 +{
 +      entity a;
 +      float b;
 +      if(IS_INDEPENDENT_PLAYER(e))
 +              return;
 +      a = e.accuracy;
 +      if(!a || !(hit || fired))
 +              return;
 +      w -= WEP_FIRST;
 +      b = accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w]));
 +      if(hit)
 +              a.(accuracy_hit[w]) += hit;
 +      if(fired)
 +              a.(accuracy_fired[w]) += fired;
 +
 +    if(hit && a.hit_time != time) // only run this once per frame
 +    {
 +        a.(accuracy_cnt_hit[w]) += 1;
 +        a.hit_time = time;
 +    }
 +
 +    if(fired && a.fired_time != time) // only run this once per frame
 +    {
 +        a.(accuracy_cnt_fired[w]) += 1;
 +        a.fired_time = time;
 +    }
 +
 +      if(b == accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w])))
 +              return;
 +      w = pow(2, mod(w, 24));
 +      a.SendFlags |= w;
 +      FOR_EACH_CLIENT(a)
 +              if(IS_SPEC(a))
 +                      if(a.enemy == e)
 +                              a.SendFlags |= w;
 +}
 +
 +float accuracy_isgooddamage(entity attacker, entity targ)
 +{
 +      if(!warmup_stage)
 +      if(IS_CLIENT(targ))
 +      if(targ.deadflag == DEAD_NO)
++      if(DIFF_TEAM(attacker, targ))
 +              return TRUE;
 +      return FALSE;
 +}
 +
 +float accuracy_canbegooddamage(entity attacker)
 +{
 +      if(!warmup_stage)
 +              return TRUE;
 +      return FALSE;
 +}