]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge remote-tracking branch 'origin/master' into samual/weapons
authorSamual Lenks <samual@xonotic.org>
Wed, 1 Jan 2014 13:11:17 +0000 (08:11 -0500)
committerSamual Lenks <samual@xonotic.org>
Wed, 1 Jan 2014 13:11:17 +0000 (08:11 -0500)
Conflicts:
mutator_new_toys.cfg
qcsrc/client/miscfunctions.qc
qcsrc/common/explosion_equation.qc
qcsrc/server/cl_weapons.qc
qcsrc/server/g_damage.qc
qcsrc/server/weapons/weaponsystem.qc

21 files changed:
1  2 
defaultXonotic.cfg
qcsrc/client/Main.qc
qcsrc/client/View.qc
qcsrc/client/autocvars.qh
qcsrc/client/hud.qc
qcsrc/client/miscfunctions.qc
qcsrc/client/scoreboard.qc
qcsrc/client/waypointsprites.qc
qcsrc/common/command/generic.qc
qcsrc/common/notifications.qh
qcsrc/common/util.qc
qcsrc/common/util.qh
qcsrc/common/weapons/calculations.qc
qcsrc/common/weapons/w_tuba.qc
qcsrc/server/cheats.qc
qcsrc/server/cl_client.qc
qcsrc/server/cl_player.qc
qcsrc/server/g_damage.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/weapons/accuracy.qc
qcsrc/server/weapons/weaponsystem.qc

Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 569073694db352e60653334104b6ba971e3dee4a,61e527cf1fbc69ade54b71368eb0e0c6fa563ab7..9b42da8aa8586ae14d58731259d3969522ad3127
@@@ -573,20 -573,17 +573,31 @@@ vector getplayerorigin(float pl
        return GETPLAYERORIGIN_ERROR;
  }
  
 +vector getcsqcplayercolor(float pl)
 +{
 +      entity e;
 +      
 +      e = CSQCModel_server2csqc(pl);
 +      if(e)
 +      {
 +              if(e.colormap > 0)
 +                      return colormapPaletteColor(((e.colormap >= 1024) ? e.colormap : stof(getplayerkeyvalue(e.colormap - 1, "colors"))) & 0x0F, TRUE);
 +      }
 +      
 +      return '1 1 1';
 +}
 +
+ float getplayeralpha(float pl)
+ {
+       entity e;
+       e = CSQCModel_server2csqc(pl + 1);
+       if(e)
+               return e.alpha;
+       return 1;
+ }
  float getplayerisdead(float pl)
  {
        entity e;
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 183a5e5ed0f829e6867b23046c8514d52f046c6c,0000000000000000000000000000000000000000..7bc64e0b5371c0ddf3fb007257f5f6af34c25480
mode 100644,000000..100644
--- /dev/null
@@@ -1,261 -1,0 +1,261 @@@
-       print(sprintf("MASS: %f\nv: %v -> %v\nENERGY BEFORE == %f + %f = %f\nENERGY AFTER >= %f\n",
 +// =============================
 +//  Explosion Force Calculation
 +// =============================
 +
 +float explosion_calcpush_getmultiplier(vector explosion_v, vector target_v)
 +{
 +      float a;
 +      a  = explosion_v * (explosion_v - target_v);
 +
 +      if(a <= 0)
 +              // target is too fast to be hittable by this
 +              return 0;
 +
 +      a /= (explosion_v * explosion_v);
 +              // we know we can divide by this, or above a would be == 0
 +
 +      return a;
 +}
 +
 +#if 0
 +vector explosion_calcpush(vector explosion_v, float explosion_m, vector target_v, float target_m, float elasticity)
 +{
 +      // solution of the equations:
 +      //    v'                = v + a vp             // central hit
 +      //    m*v'   + mp*vp'   = m*v + mp*vp          // conservation of momentum
 +      //    m*v'^2 + mp*vp'^2 = m*v^2 + mp*vp^2      // conservation of energy (ELASTIC hit)
 +      // -> a = 0                                    // case 1: did not hit
 +      // -> a = 2*mp*(vp^2 - vp.v) / ((m+mp) * vp^2) // case 2: did hit
 +      //                                             // non-elastic hits are somewhere between these two
 +
 +      // this would be physically correct, but we don't do that
 +      return explosion_v * explosion_calcpush_getmultiplier(explosion_v, target_v) * (
 +              (1 + elasticity) * (
 +                      explosion_m
 +              ) / (
 +                      target_m + explosion_m
 +              )
 +      ); // note: this factor is at least 0, at most 2
 +}
 +#endif
 +
 +// simplified formula, tuned so that if the target has velocity 0, we get exactly the original force
 +vector damage_explosion_calcpush(vector explosion_f, vector target_v, float speedfactor)
 +{
 +      // if below 1, the formulas make no sense (and would cause superjumps)
 +      if(speedfactor < 1)
 +              return explosion_f;
 +
 +#if 0
 +      float m;
 +      // find m so that
 +      //   speedfactor * (1 + e) * m / (1 + m) == 1
 +      m = 1 / ((1 + 0) * speedfactor - 1);
 +      vector v;
 +      v = explosion_calcpush(explosion_f * speedfactor, m, target_v, 1, 0);
 +      // the factor we then get is:
 +      //   1
-               (target_v + v) * (target_v + v)));
++      printf("MASS: %f\nv: %v -> %v\nENERGY BEFORE == %f + %f = %f\nENERGY AFTER >= %f\n",
 +              m,
 +              target_v, target_v + v,
 +              target_v * target_v, m * explosion_f * speedfactor * explosion_f * speedfactor, target_v * target_v + m * explosion_f * speedfactor * explosion_f * speedfactor,
++              (target_v + v) * (target_v + v));
 +      return v;
 +#endif
 +      return explosion_f * explosion_calcpush_getmultiplier(explosion_f * speedfactor, target_v);
 +}
 +
 +
 +// =========================
 +//  Shot Spread Calculation
 +// =========================
 +
 +vector cliptoplane(vector v, vector p)
 +{
 +      return v - (v * p) * p;
 +}
 +
 +vector solve_cubic_pq(float p, float q)
 +{
 +      float D, u, v, a;
 +      D = q*q/4.0 + p*p*p/27.0;
 +      if(D < 0)
 +      {
 +              // irreducibilis
 +              a = 1.0/3.0 * acos(-q/2.0 * sqrt(-27.0/(p*p*p)));
 +              u = sqrt(-4.0/3.0 * p);
 +              // a in range 0..pi/3
 +              // cos(a)
 +              // cos(a + 2pi/3)
 +              // cos(a + 4pi/3)
 +              return
 +                      u *
 +                      (
 +                              '1 0 0' * cos(a + 2.0/3.0*M_PI)
 +                              +
 +                              '0 1 0' * cos(a + 4.0/3.0*M_PI)
 +                              +
 +                              '0 0 1' * cos(a)
 +                      );
 +      }
 +      else if(D == 0)
 +      {
 +              // simple
 +              if(p == 0)
 +                      return '0 0 0';
 +              u = 3*q/p;
 +              v = -u/2;
 +              if(u >= v)
 +                      return '1 1 0' * v + '0 0 1' * u;
 +              else
 +                      return '0 1 1' * v + '1 0 0' * u;
 +      }
 +      else
 +      {
 +              // cardano
 +              u = cbrt(-q/2.0 + sqrt(D));
 +              v = cbrt(-q/2.0 - sqrt(D));
 +              return '1 1 1' * (u + v);
 +      }
 +}
 +vector solve_cubic_abcd(float a, float b, float c, float d)
 +{
 +      // y = 3*a*x + b
 +      // x = (y - b) / 3a
 +      float p, q;
 +      vector v;
 +      p = (9*a*c - 3*b*b);
 +      q = (27*a*a*d - 9*a*b*c + 2*b*b*b);
 +      v = solve_cubic_pq(p, q);
 +      v = (v -  b * '1 1 1') * (1.0 / (3.0 * a));
 +      if(a < 0)
 +              v += '1 0 -1' * (v_z - v_x); // swap x, z
 +      return v;
 +}
 +
 +vector findperpendicular(vector v)
 +{
 +      vector p;
 +      p_x = v_z;
 +      p_y = -v_x;
 +      p_z = v_y;
 +      return normalize(cliptoplane(p, v));
 +}
 +
 +vector W_CalculateSpread(vector forward, float spread, float spreadfactor, float spreadstyle)
 +{
 +      float sigma;
 +      vector v1 = '0 0 0', v2;
 +      float dx, dy, r;
 +      float sstyle;
 +      spread *= spreadfactor; //g_weaponspreadfactor;
 +      if(spread <= 0)
 +              return forward;
 +      sstyle = spreadstyle; //autocvar_g_projectiles_spread_style;
 +      
 +      if(sstyle == 0)
 +      {
 +              // this is the baseline for the spread value!
 +              // standard deviation: sqrt(2/5)
 +              // density function: sqrt(1-r^2)
 +              return forward + randomvec() * spread;
 +      }
 +      else if(sstyle == 1)
 +      {
 +              // same thing, basically
 +              return normalize(forward + cliptoplane(randomvec() * spread, forward));
 +      }
 +      else if(sstyle == 2)
 +      {
 +              // circle spread... has at sigma=1 a standard deviation of sqrt(1/2)
 +              sigma = spread * 0.89442719099991587855; // match baseline stddev
 +              v1 = findperpendicular(forward);
 +              v2 = cross(forward, v1);
 +              // random point on unit circle
 +              dx = random() * 2 * M_PI;
 +              dy = sin(dx);
 +              dx = cos(dx);
 +              // radius in our dist function
 +              r = random();
 +              r = sqrt(r);
 +              return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);
 +      }
 +      else if(sstyle == 3) // gauss 3d
 +      {
 +              sigma = spread * 0.44721359549996; // match baseline stddev
 +              // note: 2D gaussian has sqrt(2) times the stddev of 1D, so this factor is right
 +              v1 = forward;
 +              v1_x += gsl_ran_gaussian(sigma);
 +              v1_y += gsl_ran_gaussian(sigma);
 +              v1_z += gsl_ran_gaussian(sigma);
 +              return v1;
 +      }
 +      else if(sstyle == 4) // gauss 2d
 +      {
 +              sigma = spread * 0.44721359549996; // match baseline stddev
 +              // note: 2D gaussian has sqrt(2) times the stddev of 1D, so this factor is right
 +              v1_x = gsl_ran_gaussian(sigma);
 +              v1_y = gsl_ran_gaussian(sigma);
 +              v1_z = gsl_ran_gaussian(sigma);
 +              return normalize(forward + cliptoplane(v1, forward));
 +      }
 +      else if(sstyle == 5) // 1-r
 +      {
 +              sigma = spread * 1.154700538379252; // match baseline stddev
 +              v1 = findperpendicular(forward);
 +              v2 = cross(forward, v1);
 +              // random point on unit circle
 +              dx = random() * 2 * M_PI;
 +              dy = sin(dx);
 +              dx = cos(dx);
 +              // radius in our dist function
 +              r = random();
 +              r = solve_cubic_abcd(-2, 3, 0, -r) * '0 1 0';
 +              return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);
 +      }
 +      else if(sstyle == 6) // 1-r^2
 +      {
 +              sigma = spread * 1.095445115010332; // match baseline stddev
 +              v1 = findperpendicular(forward);
 +              v2 = cross(forward, v1);
 +              // random point on unit circle
 +              dx = random() * 2 * M_PI;
 +              dy = sin(dx);
 +              dx = cos(dx);
 +              // radius in our dist function
 +              r = random();
 +              r = sqrt(1 - r);
 +              r = sqrt(1 - r);
 +              return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);
 +      }
 +      else if(sstyle == 7) // (1-r) (2-r)
 +      {
 +              sigma = spread * 1.224744871391589; // match baseline stddev
 +              v1 = findperpendicular(forward);
 +              v2 = cross(forward, v1);
 +              // random point on unit circle
 +              dx = random() * 2 * M_PI;
 +              dy = sin(dx);
 +              dx = cos(dx);
 +              // radius in our dist function
 +              r = random();
 +              r = 1 - sqrt(r);
 +              r = 1 - sqrt(r);
 +              return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);
 +      }
 +      else
 +              error("g_projectiles_spread_style must be 0 (sphere), 1 (flattened sphere), 2 (circle), 3 (gauss 3D), 4 (gauss plane), 5 (linear falloff), 6 (quadratic falloff), 7 (stronger falloff)!");
 +      return '0 0 0';
 +      /*
 +       * how to derive falloff functions:
 +       * rho(r) := (2-r) * (1-r);
 +       * a : 0;
 +       * b : 1;
 +       * rhor(r) := r * rho(r);
 +       * cr(t) := integrate(rhor(r), r, a, t);
 +       * scr(t) := integrate(rhor(r) * r^2, r, a, t);
 +       * variance : scr(b) / cr(b);
 +       * solve(cr(r) = rand * cr(b), r), programmmode:false;
 +       * sqrt(0.4 / variance), numer;
 +       */
 +}
index 8adbcd024658631fb8d58d5169c736f9ff29b826,0000000000000000000000000000000000000000..9c8ea1a20222a0df972c31745f83dc0f3fa18ebb
mode 100644,000000..100644
--- /dev/null
@@@ -1,504 -1,0 +1,504 @@@
-               //print(sprintf("initial tempo rules: %f %f\n", mmin, mmax));
 +#ifdef REGISTER_WEAPON
 +REGISTER_WEAPON(
 +/* WEP_##id */ TUBA,
 +/* function */ W_Tuba,
 +/* ammotype */ ammo_none,
 +/* impulse  */ 1,
 +/* flags    */ WEP_FLAG_HIDDEN | WEP_TYPE_SPLASH,
 +/* rating   */ BOT_PICKUP_RATING_MID,
 +/* color      */ '0 1 0',
 +/* model    */ "tuba",
 +/* netname  */ "tuba",
 +/* xgettext:no-c-format */
 +/* fullname  */ _("@!#%'n Tuba")
 +);
 +
 +#define TUBA_SETTINGS(w_cvar,w_prop) TUBA_SETTINGS_LIST(w_cvar, w_prop, TUBA, tuba)
 +#define TUBA_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
 +      w_cvar(id, sn, NONE, animtime) \
 +      w_cvar(id, sn, NONE, attenuation) \
 +      w_cvar(id, sn, NONE, damage) \
 +      w_cvar(id, sn, NONE, edgedamage) \
 +      w_cvar(id, sn, NONE, force) \
 +      w_cvar(id, sn, NONE, radius) \
 +      w_cvar(id, sn, NONE, refire) \
 +      w_prop(id, sn, float,  switchdelay_raise, switchdelay_raise) \
 +      w_prop(id, sn, float,  switchdelay_drop, switchdelay_drop) \
 +      w_prop(id, sn, string, weaponreplace, weaponreplace) \
 +      w_prop(id, sn, float,  weaponstart, weaponstart) \
 +      w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride)
 +
 +#ifdef SVQC
 +TUBA_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
 +.entity tuba_note;
 +.float tuba_smoketime;
 +.float tuba_instrument;
 +
 +#define MAX_TUBANOTES 32
 +.float tuba_lastnotes_last;
 +.float tuba_lastnotes_cnt; // over
 +.vector tuba_lastnotes[MAX_TUBANOTES];
 +#endif
 +#else
 +#ifdef SVQC
 +void spawnfunc_weapon_tuba (void) { weapon_defaultspawnfunc(WEP_TUBA); }
 +
 +float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo)
 +{
 +      float i, j, mmin, mmax, nolength;
 +      float n = tokenize_console(melody);
 +      if(n > pl.tuba_lastnotes_cnt)
 +              return FALSE;
 +      float pitchshift = 0;
 +
 +      if(instrument >= 0)
 +              if(pl.tuba_instrument != instrument)
 +                      return FALSE;
 +
 +      // verify notes...
 +      nolength = FALSE;
 +      for(i = 0; i < n; ++i)
 +      {
 +              vector v = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - i + MAX_TUBANOTES, MAX_TUBANOTES)]);
 +              float ai = stof(argv(n - i - 1));
 +              float np = floor(ai);
 +              if(ai == np)
 +                      nolength = TRUE;
 +              // n counts the last played notes BACKWARDS
 +              // _x is start
 +              // _y is end
 +              // _z is note pitch
 +              if(ignorepitch && i == 0)
 +              {
 +                      pitchshift = np - v_z;
 +              }
 +              else
 +              {
 +                      if(v_z + pitchshift != np)
 +                              return FALSE;
 +              }
 +      }
 +
 +      // now we know the right NOTES were played
 +      if(!nolength)
 +      {
 +              // verify rhythm...
 +              float ti = 0;
 +              if(maxtempo > 0)
 +                      mmin = 240 / maxtempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec
 +              else
 +                      mmin = 0;
 +              if(mintempo > 0)
 +                      mmax = 240 / mintempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec
 +              else
 +                      mmax = 240; // you won't try THAT hard... (tempo 1)
-                               //print(sprintf("first note: %f to %f, should be %f\n", vi_x, vi_y, ti));
-                               //print(sprintf("second note: %f to %f, should be %f\n", vj_x, vj_y, tj));
-                               //print(sprintf("m1 = %f\n", (vi_x - vj_y) / (ti - tj)));
-                               //print(sprintf("m2 = %f\n", (vi_y - vj_x) / (ti - tj)));
++              //printf("initial tempo rules: %f %f\n", mmin, mmax);
 +
 +              for(i = 0; i < n; ++i)
 +              {
 +                      vector vi = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - i + MAX_TUBANOTES, MAX_TUBANOTES)]);
 +                      float ai = stof(argv(n - i - 1));
 +                      ti -= 1 / (ai - floor(ai));
 +                      float tj = ti;
 +                      for(j = i+1; j < n; ++j)
 +                      {
 +                              vector vj = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - j + MAX_TUBANOTES, MAX_TUBANOTES)]);
 +                              float aj = stof(argv(n - j - 1));
 +                              tj -= (aj - floor(aj));
 +
 +                              // note i should be at m*ti+b
 +                              // note j should be at m*tj+b
 +                              // so:
 +                              // we have a LINE l, so that
 +                              // vi_x <= l(ti) <= vi_y
 +                              // vj_x <= l(tj) <= vj_y
 +                              // what is m?
 +
 +                              // vi_x <= vi_y <= vj_x <= vj_y
 +                              // ti <= tj
++                              //printf("first note: %f to %f, should be %f\n", vi_x, vi_y, ti);
++                              //printf("second note: %f to %f, should be %f\n", vj_x, vj_y, tj);
++                              //printf("m1 = %f\n", (vi_x - vj_y) / (ti - tj));
++                              //printf("m2 = %f\n", (vi_y - vj_x) / (ti - tj));
 +                              mmin = max(mmin, (vi_x - vj_y) / (ti - tj)); // lower bound
 +                              mmax = min(mmax, (vi_y - vj_x) / (ti - tj)); // upper bound
 +                      }
 +              }
 +
 +              if(mmin > mmax) // rhythm fail
 +                      return FALSE;
 +      }
 +
 +      pl.tuba_lastnotes_cnt = 0;
 +
 +      return TRUE;
 +}
 +
 +void W_Tuba_NoteOff()
 +{
 +      // we have a note:
 +      //   on: self.spawnshieldtime
 +      //   off: time
 +      //   note: self.cnt
 +      if(self.owner.tuba_note == self)
 +      {
 +              self.owner.tuba_lastnotes_last = mod(self.owner.tuba_lastnotes_last + 1, MAX_TUBANOTES);
 +              self.owner.(tuba_lastnotes[self.owner.tuba_lastnotes_last]) = eX * self.spawnshieldtime + eY * time + eZ * self.cnt;
 +              self.owner.tuba_note = world;
 +              self.owner.tuba_lastnotes_cnt = bound(0, self.owner.tuba_lastnotes_cnt + 1, MAX_TUBANOTES);
 +
 +              string s;
 +              s = trigger_magicear_processmessage_forallears(self.owner, 0, world, string_null);
 +              if(s != "")
 +              {
 +                      // simulate a server message
 +                      switch(self.tuba_instrument)
 +                      {
 +                              default:
 +                              case 0: // Tuba
 +                                      bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Tuba: ^7", s, "\n"));
 +                                      break;
 +                              case 1:
 +                                      bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Accordeon: ^7", s, "\n"));
 +                                      break;
 +                              case 2:
 +                                      bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Klein Bottle: ^7", s, "\n"));
 +                                      break;
 +                      }
 +              }
 +      }
 +      remove(self);
 +}
 +
 +float W_Tuba_GetNote(entity pl, float hittype)
 +{
 +      float note;
 +      float movestate;
 +      movestate = 5;
 +      if(pl.movement_x < 0) movestate -= 3;
 +      if(pl.movement_x > 0) movestate += 3;
 +      if(pl.movement_y < 0) movestate -= 1;
 +      if(pl.movement_y > 0) movestate += 1;
 +#ifdef GMQCC
 +      note = 0;
 +#endif
 +      switch(movestate)
 +      {
 +      // layout: originally I wanted
 +      //   eb e  e#=f
 +      //   B  c  d
 +      //   Gb G  G#
 +      // but then you only use forward and right key. So to make things more
 +      // interesting, I swapped B with e#. Har har har...
 +      //   eb e  B
 +      // f=e# c  d
 +      //   Gb G  G#
 +              case 1: note = -6; break; // Gb
 +              case 2: note = -5; break; // G
 +              case 3: note = -4; break; // G#
 +              case 4: note = +5; break; // e#
 +              default:
 +              case 5: note =  0; break; // c
 +              case 6: note = +2; break; // d
 +              case 7: note = +3; break; // eb
 +              case 8: note = +4; break; // e
 +              case 9: note = -1; break; // B
 +      }
 +      if(pl.BUTTON_CROUCH)
 +              note -= 12;
 +      if(pl.BUTTON_JUMP)
 +              note += 12;
 +      if(hittype & HITTYPE_SECONDARY)
 +              note += 7;
 +
 +      // we support two kinds of tubas, those tuned in Eb and those tuned in C
 +      // kind of tuba currently is player slot number, or team number if in
 +      // teamplay
 +      // that way, holes in the range of notes are "plugged"
 +      if(teamplay)
 +      {
 +              if(pl.team == NUM_TEAM_2 || pl.team == NUM_TEAM_4)
 +                      note += 3;
 +      }
 +      else
 +      {
 +              if(pl.clientcolors & 1)
 +                      note += 3;
 +      }
 +
 +      // total range of notes:
 +      //                       0
 +      //                 ***  ** ****
 +      //                        ***  ** ****
 +      //     ***  ** ****
 +      //            ***  ** ****
 +      //     ***  ********************* ****
 +      //     -18.........................+12
 +      //        ***  ********************* ****
 +      //     -18............................+15
 +      //     with jump: ... +24
 +      //     ... +27
 +      return note;
 +}
 +
 +float W_Tuba_NoteSendEntity(entity to, float sf)
 +{
 +      float f;
 +
 +      msg_entity = to;
 +      if(!sound_allowed(MSG_ONE, self.realowner))
 +              return FALSE;
 +
 +      WriteByte(MSG_ENTITY, ENT_CLIENT_TUBANOTE);
 +      WriteByte(MSG_ENTITY, sf);
 +      if(sf & 1)
 +      {
 +              WriteChar(MSG_ENTITY, self.cnt);
 +              f = 0;
 +              if(self.realowner != to)
 +                      f |= 1;
 +              f |= 2 * self.tuba_instrument;
 +              WriteByte(MSG_ENTITY, f);
 +      }
 +      if(sf & 2)
 +      {
 +              WriteCoord(MSG_ENTITY, self.origin_x);
 +              WriteCoord(MSG_ENTITY, self.origin_y);
 +              WriteCoord(MSG_ENTITY, self.origin_z);
 +      }
 +      return TRUE;
 +}
 +
 +void W_Tuba_NoteThink()
 +{
 +      float dist_mult;
 +      float vol0, vol1;
 +      vector dir0, dir1;
 +      vector v;
 +      entity e;
 +      if(time > self.teleport_time)
 +      {
 +              W_Tuba_NoteOff();
 +              return;
 +      }
 +      self.nextthink = time;
 +      dist_mult = WEP_CVAR(tuba, attenuation) / autocvar_snd_soundradius;
 +      FOR_EACH_REALCLIENT(e)
 +      if(e != self.realowner)
 +      {
 +              v = self.origin - (e.origin + e.view_ofs);
 +              vol0 = max(0, 1 - vlen(v) * dist_mult);
 +              dir0 = normalize(v);
 +              v = self.realowner.origin - (e.origin + e.view_ofs);
 +              vol1 = max(0, 1 - vlen(v) * dist_mult);
 +              dir1 = normalize(v);
 +              if(fabs(vol0 - vol1) > 0.005) // 0.5 percent change in volume
 +              {
 +                      setorigin(self, self.realowner.origin);
 +                      self.SendFlags |= 2;
 +                      break;
 +              }
 +              if(dir0 * dir1 < 0.9994) // 2 degrees change in angle
 +              {
 +                      setorigin(self, self.realowner.origin);
 +                      self.SendFlags |= 2;
 +                      break;
 +              }
 +      }
 +}
 +
 +void W_Tuba_NoteOn(float hittype)
 +{
 +      vector o;
 +      float n;
 +
 +      W_SetupShot(self, FALSE, 2, "", 0, WEP_CVAR(tuba, damage));
 +
 +      n = W_Tuba_GetNote(self, hittype);
 +
 +      hittype = 0;
 +      if(self.tuba_instrument & 1)
 +              hittype |= HITTYPE_SECONDARY;
 +      if(self.tuba_instrument & 2)
 +              hittype |= HITTYPE_BOUNCE;
 +
 +      if(self.tuba_note)
 +      {
 +              if(self.tuba_note.cnt != n || self.tuba_note.tuba_instrument != self.tuba_instrument)
 +              {
 +                      entity oldself = self;
 +                      self = self.tuba_note;
 +                      W_Tuba_NoteOff();
 +                      self = oldself;
 +              }
 +      }
 +
 +      if (!self.tuba_note)
 +      {
 +              self.tuba_note = spawn();
 +              self.tuba_note.owner = self.tuba_note.realowner = self;
 +              self.tuba_note.cnt = n;
 +              self.tuba_note.tuba_instrument = self.tuba_instrument;
 +              self.tuba_note.think = W_Tuba_NoteThink;
 +              self.tuba_note.nextthink = time;
 +              self.tuba_note.spawnshieldtime = time;
 +              Net_LinkEntity(self.tuba_note, FALSE, 0, W_Tuba_NoteSendEntity);
 +      }
 +
 +      self.tuba_note.teleport_time = time + WEP_CVAR(tuba, refire) * 2 * W_WeaponRateFactor(); // so it can get prolonged safely
 +
 +      //sound(self, c, TUBA_NOTE(n), bound(0, VOL_BASE * cvar("g_balance_tuba_volume"), 1), autocvar_g_balance_tuba_attenuation);
 +      RadiusDamage(self, self, WEP_CVAR(tuba, damage), WEP_CVAR(tuba, edgedamage), WEP_CVAR(tuba, radius), world, world, WEP_CVAR(tuba, force), hittype | WEP_TUBA, world);
 +
 +      o = gettaginfo(self.exteriorweaponentity, 0);
 +      if(time > self.tuba_smoketime)
 +      {
 +              pointparticles(particleeffectnum("smoke_ring"), o + v_up * 45 + v_right * -6 + v_forward * 8, v_up * 100, 1);
 +              self.tuba_smoketime = time + 0.25;
 +      }
 +}
 +
 +float W_Tuba(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_AIM:
 +              {
 +                      // bots cannot play the Tuba well yet
 +                      // I think they should start with the recorder first
 +                      if(vlen(self.origin - self.enemy.origin) < WEP_CVAR(tuba, radius))
 +                      {
 +                              if(random() > 0.5)
 +                                      self.BUTTON_ATCK = 1;
 +                              else
 +                                      self.BUTTON_ATCK2 = 1;
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_THINK:
 +              {
 +                      if (self.BUTTON_ATCK)
 +                      if (weapon_prepareattack(0, WEP_CVAR(tuba, refire)))
 +                      {
 +                              W_Tuba_NoteOn(0);
 +                              //weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_tuba_animtime, w_ready);
 +                              weapon_thinkf(WFRAME_IDLE, WEP_CVAR(tuba, animtime), w_ready);
 +                      }
 +                      if (self.BUTTON_ATCK2)
 +                      if (weapon_prepareattack(1, WEP_CVAR(tuba, refire)))
 +                      {
 +                              W_Tuba_NoteOn(HITTYPE_SECONDARY);
 +                              //weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_tuba_animtime, w_ready);
 +                              weapon_thinkf(WFRAME_IDLE, WEP_CVAR(tuba, animtime), w_ready);
 +                      }
 +                      if(self.tuba_note)
 +                      {
 +                              if(!self.BUTTON_ATCK && !self.BUTTON_ATCK2)
 +                              {
 +                                      entity oldself = self;
 +                                      self = self.tuba_note;
 +                                      W_Tuba_NoteOff();
 +                                      self = oldself;
 +                              }
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_model ("models/weapons/g_tuba.md3");
 +                      precache_model ("models/weapons/v_tuba.md3");
 +                      precache_model ("models/weapons/h_tuba.iqm");
 +                      precache_model ("models/weapons/v_akordeon.md3");
 +                      precache_model ("models/weapons/h_akordeon.iqm");
 +                      precache_model ("models/weapons/v_kleinbottle.md3");
 +                      precache_model ("models/weapons/h_kleinbottle.iqm");
 +                      TUBA_SETTINGS(WEP_SKIPCVAR, WEP_SET_PROP)
 +                      return TRUE;
 +              }
 +              case WR_SETUP:
 +              {
 +                      self.ammo_field = ammo_none;
 +                      self.tuba_instrument = 0;
 +                      return TRUE;
 +              }
 +              case WR_RELOAD:
 +              {
 +                      // switch to alternate instruments :)
 +                      if(self.weaponentity.state == WS_READY)
 +                      {
 +                              switch(self.tuba_instrument)
 +                              {
 +                                      case 0:
 +                                              self.tuba_instrument = 1;
 +                                              self.weaponname = "akordeon";
 +                                              break;
 +                                      case 1:
 +                                              self.tuba_instrument = 2;
 +                                              self.weaponname = "kleinbottle";
 +                                              break;
 +                                      case 2:
 +                                              self.tuba_instrument = 0;
 +                                              self.weaponname = "tuba";
 +                                              break;
 +                              }
 +                              W_SetupShot(self, FALSE, 0, "", 0, 0);
 +                              pointparticles(particleeffectnum("teleport"), w_shotorg, '0 0 0', 1);
 +                              self.weaponentity.state = WS_INUSE;
 +                              weapon_thinkf(WFRAME_RELOAD, 0.5, w_ready);
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_CHECKAMMO1:
 +              case WR_CHECKAMMO2:
 +              {
 +                      return TRUE; // tuba has infinite ammo
 +              }
 +              case WR_CONFIG:
 +              {
 +                      TUBA_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
 +                      return TRUE;
 +              }
 +              case WR_SUICIDEMESSAGE:
 +              {
 +                      if(w_deathtype & HITTYPE_BOUNCE)
 +                              return WEAPON_KLEINBOTTLE_SUICIDE;
 +                      else if(w_deathtype & HITTYPE_SECONDARY)
 +                              return WEAPON_ACCORDEON_SUICIDE;
 +                      else
 +                              return WEAPON_TUBA_SUICIDE;
 +              }
 +              case WR_KILLMESSAGE:
 +              {
 +                      if(w_deathtype & HITTYPE_BOUNCE)
 +                              return WEAPON_KLEINBOTTLE_MURDER;
 +                      else if(w_deathtype & HITTYPE_SECONDARY)
 +                              return WEAPON_ACCORDEON_MURDER;
 +                      else
 +                              return WEAPON_TUBA_MURDER;
 +              }
 +      }
 +      return TRUE;
 +}
 +#endif
 +#ifdef CSQC
 +float W_Tuba(float req)
 +{
 +      // nothing to do here; particles of tuba are handled differently
 +      // WEAPONTODO
 +
 +      switch(req)
 +      {
 +              case WR_ZOOMRETICLE:
 +              {
 +                      // no weapon specific image for this weapon
 +                      return FALSE;
 +              }
 +      }
 +
 +      return TRUE;
 +}
 +#endif
 +#endif
Simple merge
Simple merge
Simple merge
index b9d2ab9aae78052befe77b1cbfd31e56d78e37e1,2ee79c407f2a16092a8d580d11fdefc70a070027..96e4d0af1a201fdd2f0e1ef0c0b298e9cf2e6200
@@@ -862,130 -864,176 +862,132 @@@ float RadiusDamageForSource (entity inf
        while (targ)
        {
                next = targ.chain;
 -              if (targ != inflictor)
 -                      if (ignore != targ) if(targ.takedamage)
 +              if ((targ != inflictor) || inflictorselfdamage)
 +              if (((cantbe != targ) && !mustbe) || (mustbe == targ))
 +              if (targ.takedamage)
 +              {
 +                      vector nearest;
 +                      vector diff;
 +                      float power;
 +
 +                      // LordHavoc: measure distance to nearest point on target (not origin)
 +                      // (this guarentees 100% damage on a touch impact)
 +                      nearest = targ.WarpZone_findradius_nearest;
 +                      diff = targ.WarpZone_findradius_dist;
 +                      // round up a little on the damage to ensure full damage on impacts
 +                      // and turn the distance into a fraction of the radius
 +                      power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
 +                      //bprint(" ");
 +                      //bprint(ftos(power));
 +                      //if (targ == attacker)
 +                      //      print(ftos(power), "\n");
 +                      if (power > 0)
                        {
 -                              vector nearest;
 -                              vector diff;
 -                              float power;
 -
 -                              // LordHavoc: measure distance to nearest point on target (not origin)
 -                              // (this guarentees 100% damage on a touch impact)
 -                              nearest = targ.WarpZone_findradius_nearest;
 -                              diff = targ.WarpZone_findradius_dist;
 -                              // round up a little on the damage to ensure full damage on impacts
 -                              // and turn the distance into a fraction of the radius
 -                              power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
 -                              //bprint(" ");
 -                              //bprint(ftos(power));
 -                              //if (targ == attacker)
 -                              //      print(ftos(power), "\n");
 -                              if (power > 0)
 +                              float finaldmg;
 +                              if (power > 1)
 +                                      power = 1;
 +                              finaldmg = coredamage * power + edgedamage * (1 - power);
 +                              if (finaldmg > 0)
                                {
 -                                      float finaldmg;
 -                                      if (power > 1)
 -                                              power = 1;
 -                                      finaldmg = coredamage * power + edgedamage * (1 - power);
 -                                      if (finaldmg > 0)
 -                                      {
 -                                              float a;
 -                                              float c;
 -                                              vector hitloc;
 -                                              vector myblastorigin;
 -                                              vector center;
 +                                      float a;
 +                                      float c;
 +                                      vector hitloc;
 +                                      vector myblastorigin;
 +                                      vector center;
  
 -                                              myblastorigin = WarpZone_TransformOrigin(targ, blastorigin);
 +                                      myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
  
 -                                              // if it's a player, use the view origin as reference
 -                                              center = CENTER_OR_VIEWOFS(targ);
 +                                      // if it's a player, use the view origin as reference
 +                                      center = CENTER_OR_VIEWOFS(targ);
  
 -                                              force = normalize(center - myblastorigin);
 -                                              force = force * (finaldmg / coredamage) * forceintensity;
 -                                              hitloc = nearest;
 -
 -                                              if(targ != directhitentity)
 -                                              {
 -                                                      float hits;
 -                                                      float total;
 -                                                      float hitratio;
 -                                                      float mininv_f, mininv_d;
 +                                      force = normalize(center - myblastorigin);
 +                                      force = force * (finaldmg / coredamage) * forceintensity;
 +                                      hitloc = nearest;
  
 -                                                      // test line of sight to multiple positions on box,
 -                                                      // and do damage if any of them hit
 -                                                      hits = 0;
 +                                      if(targ != directhitentity)
 +                                      {
 +                                              float hits;
 +                                              float total;
 +                                              float hitratio;
 +                                              float mininv_f, mininv_d;
  
 -                                                      // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
 -                                                      // so for a given max stddev:
 -                                                      // n = (1 / (2 * max stddev of hitratio))^2
 +                                              // test line of sight to multiple positions on box,
 +                                              // and do damage if any of them hit
 +                                              hits = 0;
  
 -                                                      mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
 -                                                      mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
 +                                              // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
 +                                              // so for a given max stddev:
 +                                              // n = (1 / (2 * max stddev of hitratio))^2
  
 -                                                      if(autocvar_g_throughfloor_debug)
 -                                                              printf("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
 +                                              mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
 +                                              mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
  
 -                                                      total = 0.25 * pow(max(mininv_f, mininv_d), 2);
 +                                              if(autocvar_g_throughfloor_debug)
-                                                       print(sprintf("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f));
++                                                      printf("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
 -                                                      if(autocvar_g_throughfloor_debug)
 -                                                              printf(" steps=%f", total);
  
 -                                                      if (IS_PLAYER(targ))
 -                                                              total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
 -                                                      else
 -                                                              total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
 +                                              total = 0.25 * pow(max(mininv_f, mininv_d), 2);
  
 -                                                      if(autocvar_g_throughfloor_debug)
 -                                                              printf(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
 +                                              if(autocvar_g_throughfloor_debug)
-                                                       print(sprintf(" steps=%f", total));
++                                                      printf(" steps=%f", total);
 -                                                      for(c = 0; c < total; ++c)
 -                                                      {
 -                                                              //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
 -                                                              WarpZone_TraceLine(blastorigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
 -                                                              if (trace_fraction == 1 || trace_ent == targ)
 -                                                              {
 -                                                                      ++hits;
 -                                                                      if (hits > 1)
 -                                                                              hitloc = hitloc + nearest;
 -                                                                      else
 -                                                                              hitloc = nearest;
 -                                                              }
 -                                                              nearest_x = targ.origin_x + targ.mins_x + random() * targ.size_x;
 -                                                              nearest_y = targ.origin_y + targ.mins_y + random() * targ.size_y;
 -                                                              nearest_z = targ.origin_z + targ.mins_z + random() * targ.size_z;
 -                                                      }
  
 -                                                      nearest = hitloc * (1 / max(1, hits));
 -                                                      hitratio = (hits / total);
 -                                                      a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
 -                                                      finaldmg = finaldmg * a;
 -                                                      a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
 -                                                      force = force * a;
 +                                              if (IS_PLAYER(targ))
 +                                                      total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
 +                                              else
 +                                                      total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
  
 -                                                      if(autocvar_g_throughfloor_debug)
 -                                                              printf(" D=%f F=%f\n", finaldmg, vlen(force));
 -                                              }
 +                                              if(autocvar_g_throughfloor_debug)
-                                                       print(sprintf(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total))));
++                                                      printf(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
  
 -                                              // laser force adjustments :P
 -                                              if(DEATH_WEAPONOF(deathtype) == WEP_LASER)
 +                                              for(c = 0; c < total; ++c)
                                                {
 -                                                      if (targ == attacker)
 -                                                      {
 -                                                              vector vel;
 -
 -                                                              float force_zscale;
 -                                                              float force_velocitybiasramp;
 -                                                              float force_velocitybias;
 -
 -                                                              force_velocitybiasramp = autocvar_sv_maxspeed;
 -                                                              if(deathtype & HITTYPE_SECONDARY)
 -                                                              {
 -                                                                      force_zscale = autocvar_g_balance_laser_secondary_force_zscale;
 -                                                                      force_velocitybias = autocvar_g_balance_laser_secondary_force_velocitybias;
 -                                                              }
 -                                                              else
 -                                                              {
 -                                                                      force_zscale = autocvar_g_balance_laser_primary_force_zscale;
 -                                                                      force_velocitybias = autocvar_g_balance_laser_primary_force_velocitybias;
 -                                                              }
 -
 -                                                              vel = targ.velocity;
 -                                                              vel_z = 0;
 -                                                              vel = normalize(vel) * bound(0, vlen(vel) / force_velocitybiasramp, 1) * force_velocitybias;
 -                                                              force =
 -                                                                      vlen(force)
 -                                                                      *
 -                                                                      normalize(normalize(force) + vel);
 -
 -                                                              force_z *= force_zscale;
 -                                                      }
 -                                                      else
 +                                                      //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
 +                                                      WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
 +                                                      if (trace_fraction == 1 || trace_ent == targ)
                                                        {
 -                                                              if(deathtype & HITTYPE_SECONDARY)
 -                                                              {
 -                                                                      force *= autocvar_g_balance_laser_secondary_force_other_scale;
 -                                                              }
 +                                                              ++hits;
 +                                                              if (hits > 1)
 +                                                                      hitloc = hitloc + nearest;
                                                                else
 -                                                              {
 -                                                                      force *= autocvar_g_balance_laser_primary_force_other_scale;
 -                                                              }
 +                                                                      hitloc = nearest;
                                                        }
 +                                                      nearest_x = targ.origin_x + targ.mins_x + random() * targ.size_x;
 +                                                      nearest_y = targ.origin_y + targ.mins_y + random() * targ.size_y;
 +                                                      nearest_z = targ.origin_z + targ.mins_z + random() * targ.size_z;
                                                }
  
 -                                              //if (targ == attacker)
 -                                              //{
 -                                              //      print("hits ", ftos(hits), " / ", ftos(total));
 -                                              //      print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
 -                                              //      print(" (", ftos(a), ")\n");
 -                                              //}
 -                                              if(finaldmg || vlen(force))
 -                                              {
 -                                                      if(targ.iscreature)
 -                                                      {
 -                                                              total_damage_to_creatures += finaldmg;
 +                                              nearest = hitloc * (1 / max(1, hits));
 +                                              hitratio = (hits / total);
 +                                              a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
 +                                              finaldmg = finaldmg * a;
 +                                              a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
 +                                              force = force * a;
  
 -                                                              if(accuracy_isgooddamage(attacker, targ))
 -                                                                      stat_damagedone += finaldmg;
 -                                                      }
 +                                              if(autocvar_g_throughfloor_debug)
-                                                       print(sprintf(" D=%f F=%f\n", finaldmg, vlen(force)));
++                                                      printf(" D=%f F=%f\n", finaldmg, vlen(force));
 +                                      }
  
 -                                                      if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
 -                                                              Damage (targ, inflictor, attacker, finaldmg, deathtype, nearest, force);
 -                                                      else
 -                                                              Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, nearest, force);
 +                                      //if (targ == attacker)
 +                                      //{
 +                                      //      print("hits ", ftos(hits), " / ", ftos(total));
 +                                      //      print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
 +                                      //      print(" (", ftos(a), ")\n");
 +                                      //}
 +                                      if(finaldmg || vlen(force))
 +                                      {
 +                                              if(targ.iscreature)
 +                                              {
 +                                                      total_damage_to_creatures += finaldmg;
 +
 +                                                      if(accuracy_isgooddamage(attacker, targ))
 +                                                              stat_damagedone += finaldmg;
                                                }
 +
 +                                              if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
 +                                                      Damage (targ, inflictor, attacker, finaldmg, deathtype, nearest, force);
 +                                              else
 +                                                      Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, nearest, force);
                                        }
                                }
                        }
Simple merge
index 00605ce6b966850b90cc5a8b870f2ab99af45524,0000000000000000000000000000000000000000..5b22669ce1c0bf04b49930f67a7e42cdc1ad57ae
mode 100644,000000..100644
--- /dev/null
@@@ -1,120 -1,0 +1,120 @@@
-       //print(sprintf("accuracy: %d / %d\n", n, d));
 +float accuracy_byte(float n, float d)
 +{
++      //printf("accuracy: %d / %d\n", n, d);
 +      if(n <= 0)
 +              return 0;
 +      if(n > d)
 +              return 255;
 +      return 1 + rint(n * 100.0 / d);
 +}
 +
 +float accuracy_send(entity to, float sf)
 +{
 +      float w, f;
 +      entity a;
 +      WriteByte(MSG_ENTITY, ENT_CLIENT_ACCURACY);
 +
 +      a = self.owner;
 +      if(IS_SPEC(a))
 +              a = a.enemy;
 +      a = a.accuracy;
 +
 +      if(to != a.owner)
 +              if (!(self.owner.cvar_cl_accuracy_data_share && autocvar_sv_accuracy_data_share))
 +                      sf = 0;
 +      // note: zero sendflags can never be sent... so we can use that to say that we send no accuracy!
 +      WriteInt24_t(MSG_ENTITY, sf);
 +      if(sf == 0)
 +              return TRUE;
 +      // note: we know that client and server agree about SendFlags...
 +      for(w = 0, f = 1; w <= WEP_LAST - WEP_FIRST; ++w)
 +      {
 +              if(sf & f)
 +                      WriteByte(MSG_ENTITY, accuracy_byte(self.(accuracy_hit[w]), self.(accuracy_fired[w])));
 +              if(f == 0x800000)
 +                      f = 1;
 +              else
 +                      f *= 2;
 +      }
 +      return TRUE;
 +}
 +
 +// init/free
 +void accuracy_init(entity e)
 +{
 +      e.accuracy = spawn();
 +      e.accuracy.owner = e;
 +      e.accuracy.classname = "accuracy";
 +      e.accuracy.drawonlytoclient = e;
 +      Net_LinkEntity(e.accuracy, FALSE, 0, accuracy_send);
 +}
 +
 +void accuracy_free(entity e)
 +{
 +      remove(e.accuracy);
 +}
 +
 +// force a resend of a player's accuracy stats
 +void accuracy_resend(entity e)
 +{
 +      e.accuracy.SendFlags = 0xFFFFFF;
 +}
 +
 +// update accuracy stats
 +.float hit_time;
 +.float fired_time;
 +
 +void accuracy_add(entity e, float w, float fired, float hit)
 +{
 +      entity a;
 +      float b;
 +      if(IS_INDEPENDENT_PLAYER(e))
 +              return;
 +      a = e.accuracy;
 +      if(!a || !(hit || fired))
 +              return;
 +      w -= WEP_FIRST;
 +      b = accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w]));
 +      if(hit)
 +              a.(accuracy_hit[w]) += hit;
 +      if(fired)
 +              a.(accuracy_fired[w]) += fired;
 +
 +    if(hit && a.hit_time != time) // only run this once per frame
 +    {
 +        a.(accuracy_cnt_hit[w]) += 1;
 +        a.hit_time = time;
 +    }
 +
 +    if(fired && a.fired_time != time) // only run this once per frame
 +    {
 +        a.(accuracy_cnt_fired[w]) += 1;
 +        a.fired_time = time;
 +    }
 +
 +      if(b == accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w])))
 +              return;
 +      w = pow(2, mod(w, 24));
 +      a.SendFlags |= w;
 +      FOR_EACH_CLIENT(a)
 +              if(IS_SPEC(a))
 +                      if(a.enemy == e)
 +                              a.SendFlags |= w;
 +}
 +
 +float accuracy_isgooddamage(entity attacker, entity targ)
 +{
 +      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;
 +}
index da98d7f1cad869718334d004e52299ba4780d5dd,0000000000000000000000000000000000000000..9ac6202fdce2991ca60ed2d2cbf3848c7d9ced61
mode 100644,000000..100644
--- /dev/null
@@@ -1,934 -1,0 +1,932 @@@
-               //print(sprintf("CL_Weaponentity_Think(): cvar: %s, value: %f, nextthink: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname)), (self.owner.weapon_nextthink - time)));
 +/*
 +===========================================================================
 +
 +  CLIENT WEAPONSYSTEM CODE
 +  Bring back W_Weaponframe
 +
 +===========================================================================
 +*/
 +
 +.float weapon_frametime;
 +
 +float W_WeaponRateFactor()
 +{
 +      float t;
 +      t = 1.0 / g_weaponratefactor;
 +
 +      return t;
 +}
 +
 +// VorteX: static frame globals
 +const float WFRAME_DONTCHANGE = -1;
 +const float WFRAME_FIRE1 = 0;
 +const float WFRAME_FIRE2 = 1;
 +const float WFRAME_IDLE = 2;
 +const float WFRAME_RELOAD = 3;
 +.float wframe;
 +
 +void(float fr, float t, void() func) weapon_thinkf;
 +
 +float CL_Weaponentity_CustomizeEntityForClient()
 +{
 +      self.viewmodelforclient = self.owner;
 +      if(IS_SPEC(other))
 +              if(other.enemy == self.owner)
 +                      self.viewmodelforclient = other;
 +      return TRUE;
 +}
 +
 +/*
 + * supported formats:
 + *
 + * 1. simple animated model, muzzle flash handling on h_ model:
 + *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
 + *      tags:
 + *        shot = muzzle end (shot origin, also used for muzzle flashes)
 + *        shell = casings ejection point (must be on the right hand side of the gun)
 + *        weapon = attachment for v_tuba.md3
 + *    v_tuba.md3 - first and third person model
 + *    g_tuba.md3 - pickup model
 + *
 + * 2. simple animated model, muzzle flash handling on v_ model:
 + *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
 + *      tags:
 + *        weapon = attachment for v_tuba.md3
 + *    v_tuba.md3 - first and third person model
 + *      tags:
 + *        shot = muzzle end (shot origin, also used for muzzle flashes)
 + *        shell = casings ejection point (must be on the right hand side of the gun)
 + *    g_tuba.md3 - pickup model
 + *
 + * 3. fully animated model, muzzle flash handling on h_ model:
 + *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
 + *      tags:
 + *        shot = muzzle end (shot origin, also used for muzzle flashes)
 + *        shell = casings ejection point (must be on the right hand side of the gun)
 + *        handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)
 + *    v_tuba.md3 - third person model
 + *    g_tuba.md3 - pickup model
 + *
 + * 4. fully animated model, muzzle flash handling on v_ model:
 + *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
 + *      tags:
 + *        shot = muzzle end (shot origin)
 + *        shell = casings ejection point (must be on the right hand side of the gun)
 + *    v_tuba.md3 - third person model
 + *      tags:
 + *        shot = muzzle end (for muzzle flashes)
 + *    g_tuba.md3 - pickup model
 + */
 +
 +// writes:
 +//   self.origin, self.angles
 +//   self.weaponentity
 +//   self.movedir, self.view_ofs
 +//   attachment stuff
 +//   anim stuff
 +// to free:
 +//   call again with ""
 +//   remove the ent
 +void CL_WeaponEntity_SetModel(string name)
 +{
 +      float v_shot_idx;
 +      if (name != "")
 +      {
 +              // if there is a child entity, hide it until we're sure we use it
 +              if (self.weaponentity)
 +                      self.weaponentity.model = "";
 +              setmodel(self, strcat("models/weapons/v_", name, ".md3")); // precision set below
 +              v_shot_idx = gettagindex(self, "shot"); // used later
 +              if(!v_shot_idx)
 +                      v_shot_idx = gettagindex(self, "tag_shot");
 +
 +              setmodel(self, strcat("models/weapons/h_", name, ".iqm")); // precision set below
 +              // preset some defaults that work great for renamed zym files (which don't need an animinfo)
 +              self.anim_fire1  = animfixfps(self, '0 1 0.01', '0 0 0');
 +              self.anim_fire2  = animfixfps(self, '1 1 0.01', '0 0 0');
 +              self.anim_idle   = animfixfps(self, '2 1 0.01', '0 0 0');
 +              self.anim_reload = animfixfps(self, '3 1 0.01', '0 0 0');
 +
 +              // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)
 +              // if we don't, this is a "real" animated model
 +              if(gettagindex(self, "weapon"))
 +              {
 +                      if (!self.weaponentity)
 +                              self.weaponentity = spawn();
 +                      setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
 +                      setattachment(self.weaponentity, self, "weapon");
 +              }
 +              else if(gettagindex(self, "tag_weapon"))
 +              {
 +                      if (!self.weaponentity)
 +                              self.weaponentity = spawn();
 +                      setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
 +                      setattachment(self.weaponentity, self, "tag_weapon");
 +              }
 +              else
 +              {
 +                      if(self.weaponentity)
 +                              remove(self.weaponentity);
 +                      self.weaponentity = world;
 +              }
 +
 +              setorigin(self,'0 0 0');
 +              self.angles = '0 0 0';
 +              self.frame = 0;
 +              self.viewmodelforclient = world;
 +
 +              float idx;
 +
 +              if(v_shot_idx) // v_ model attached to invisible h_ model
 +              {
 +                      self.movedir = gettaginfo(self.weaponentity, v_shot_idx);
 +              }
 +              else
 +              {
 +                      idx = gettagindex(self, "shot");
 +                      if(!idx)
 +                              idx = gettagindex(self, "tag_shot");
 +                      if(idx)
 +                              self.movedir = gettaginfo(self, idx);
 +                      else
 +                      {
 +                              print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n");
 +                              self.movedir = '0 0 0';
 +                      }
 +              }
 +
 +              if(self.weaponentity) // v_ model attached to invisible h_ model
 +              {
 +                      idx = gettagindex(self.weaponentity, "shell");
 +                      if(!idx)
 +                              idx = gettagindex(self.weaponentity, "tag_shell");
 +                      if(idx)
 +                              self.spawnorigin = gettaginfo(self.weaponentity, idx);
 +              }
 +              else
 +                      idx = 0;
 +              if(!idx)
 +              {
 +                      idx = gettagindex(self, "shell");
 +                      if(!idx)
 +                              idx = gettagindex(self, "tag_shell");
 +                      if(idx)
 +                              self.spawnorigin = gettaginfo(self, idx);
 +                      else
 +                      {
 +                              print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");
 +                              self.spawnorigin = self.movedir;
 +                      }
 +              }
 +
 +              if(v_shot_idx)
 +              {
 +                      self.oldorigin = '0 0 0'; // use regular attachment
 +              }
 +              else
 +              {
 +                      if(self.weaponentity)
 +                      {
 +                              idx = gettagindex(self, "weapon");
 +                              if(!idx)
 +                                      idx = gettagindex(self, "tag_weapon");
 +                      }
 +                      else
 +                      {
 +                              idx = gettagindex(self, "handle");
 +                              if(!idx)
 +                                      idx = gettagindex(self, "tag_handle");
 +                      }
 +                      if(idx)
 +                      {
 +                              self.oldorigin = self.movedir - gettaginfo(self, idx);
 +                      }
 +                      else
 +                      {
 +                              print("WARNING: weapon model ", self.model, " does not support the 'handle' tag and neither does the v_ model support the 'shot' tag, will display muzzle flashes TOTALLY wrong\n");
 +                              self.oldorigin = '0 0 0'; // there is no way to recover from this
 +                      }
 +              }
 +
 +              self.viewmodelforclient = self.owner;
 +      }
 +      else
 +      {
 +              self.model = "";
 +              if(self.weaponentity)
 +                      remove(self.weaponentity);
 +              self.weaponentity = world;
 +              self.movedir = '0 0 0';
 +              self.spawnorigin = '0 0 0';
 +              self.oldorigin = '0 0 0';
 +              self.anim_fire1  = '0 1 0.01';
 +              self.anim_fire2  = '0 1 0.01';
 +              self.anim_idle   = '0 1 0.01';
 +              self.anim_reload = '0 1 0.01';
 +      }
 +
 +      self.view_ofs = '0 0 0';
 +
 +      if(self.movedir_x >= 0)
 +      {
 +              vector v0;
 +              v0 = self.movedir;
 +              self.movedir = shotorg_adjust(v0, FALSE, FALSE);
 +              self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;
 +      }
 +      self.owner.stat_shotorg = compressShotOrigin(self.movedir);
 +      self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly
 +
 +      self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount
 +
 +      // check if an instant weapon switch occurred
 +      setorigin(self, self.view_ofs);
 +      // reset animstate now
 +      self.wframe = WFRAME_IDLE;
 +      setanim(self, self.anim_idle, TRUE, FALSE, TRUE);
 +}
 +
 +vector CL_Weapon_GetShotOrg(float wpn)
 +{
 +      entity wi, oldself;
 +      vector ret;
 +      wi = get_weaponinfo(wpn);
 +      oldself = self;
 +      self = spawn();
 +      CL_WeaponEntity_SetModel(wi.mdl);
 +      ret = self.movedir;
 +      CL_WeaponEntity_SetModel("");
 +      remove(self);
 +      self = oldself;
 +      return ret;
 +}
 +
 +void CL_Weaponentity_Think()
 +{
 +      float tb;
 +      self.nextthink = time;
 +      if (intermission_running)
 +              self.frame = self.anim_idle_x;
 +      if (self.owner.weaponentity != self)
 +      {
 +              if (self.weaponentity)
 +                      remove(self.weaponentity);
 +              remove(self);
 +              return;
 +      }
 +      if (self.owner.deadflag != DEAD_NO)
 +      {
 +              self.model = "";
 +              if (self.weaponentity)
 +                      self.weaponentity.model = "";
 +              return;
 +      }
 +      if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
 +      {
 +              self.weaponname = self.owner.weaponname;
 +              self.dmg = self.owner.modelindex;
 +              self.deadflag = self.owner.deadflag;
 +
 +              CL_WeaponEntity_SetModel(self.owner.weaponname);
 +      }
 +
 +      tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT));
 +      self.effects = self.owner.effects & EFMASK_CHEAP;
 +      self.effects &= ~EF_LOWPRECISION;
 +      self.effects &= ~EF_FULLBRIGHT; // can mask team color, so get rid of it
 +      self.effects &= ~EF_TELEPORT_BIT;
 +      self.effects &= ~EF_RESTARTANIM_BIT;
 +      self.effects |= tb;
 +
 +      if(self.owner.alpha == default_player_alpha)
 +              self.alpha = default_weapon_alpha;
 +      else if(self.owner.alpha != 0)
 +              self.alpha = self.owner.alpha;
 +      else
 +              self.alpha = 1;
 +
 +      self.glowmod = self.owner.weaponentity_glowmod;
 +      self.colormap = self.owner.colormap;
 +      if (self.weaponentity)
 +      {
 +              self.weaponentity.effects = self.effects;
 +              self.weaponentity.alpha = self.alpha;
 +              self.weaponentity.colormap = self.colormap;
 +              self.weaponentity.glowmod = self.glowmod;
 +      }
 +
 +      self.angles = '0 0 0';
 +
 +      float f = (self.owner.weapon_nextthink - time);
 +      if (self.state == WS_RAISE && !intermission_running)
 +      {
 +              entity newwep = get_weaponinfo(self.owner.switchweapon);
 +              f = f * g_weaponratefactor / max(f, newwep.switchdelay_raise);
-               //print(sprintf("CL_Weaponentity_Think(): cvar: %s, value: %f, nextthink: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname)), (self.owner.weapon_nextthink - time)));
 +              self.angles_x = -90 * f * f;
 +      }
 +      else if (self.state == WS_DROP && !intermission_running)
 +      {
 +              entity oldwep = get_weaponinfo(self.owner.weapon);
 +              f = 1 - f * g_weaponratefactor / max(f, oldwep.switchdelay_drop);
 +              self.angles_x = -90 * f * f;
 +      }
 +      else if (self.state == WS_CLEAR)
 +      {
 +              f = 1;
 +              self.angles_x = -90 * f * f;
 +      }
 +}
 +
 +void CL_ExteriorWeaponentity_Think()
 +{
 +      float tag_found;
 +      self.nextthink = time;
 +      if (self.owner.exteriorweaponentity != self)
 +      {
 +              remove(self);
 +              return;
 +      }
 +      if (self.owner.deadflag != DEAD_NO)
 +      {
 +              self.model = "";
 +              return;
 +      }
 +      if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
 +      {
 +              self.weaponname = self.owner.weaponname;
 +              self.dmg = self.owner.modelindex;
 +              self.deadflag = self.owner.deadflag;
 +              if (self.owner.weaponname != "")
 +                      setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
 +              else
 +                      self.model = "";
 +
 +              if((tag_found = gettagindex(self.owner, "tag_weapon")))
 +              {
 +                      self.tag_index = tag_found;
 +                      self.tag_entity = self.owner;
 +              }
 +              else
 +                      setattachment(self, self.owner, "bip01 r hand");
 +      }
 +      self.effects = self.owner.effects;
 +      self.effects |= EF_LOWPRECISION;
 +      self.effects = self.effects & EFMASK_CHEAP; // eat performance
 +      if(self.owner.alpha == default_player_alpha)
 +              self.alpha = default_weapon_alpha;
 +      else if(self.owner.alpha != 0)
 +              self.alpha = self.owner.alpha;
 +      else
 +              self.alpha = 1;
 +
 +      self.glowmod = self.owner.weaponentity_glowmod;
 +      self.colormap = self.owner.colormap;
 +
 +      CSQCMODEL_AUTOUPDATE();
 +}
 +
 +// spawning weaponentity for client
 +void CL_SpawnWeaponentity()
 +{
 +      self.weaponentity = spawn();
 +      self.weaponentity.classname = "weaponentity";
 +      self.weaponentity.solid = SOLID_NOT;
 +      self.weaponentity.owner = self;
 +      setmodel(self.weaponentity, ""); // precision set when changed
 +      setorigin(self.weaponentity, '0 0 0');
 +      self.weaponentity.angles = '0 0 0';
 +      self.weaponentity.viewmodelforclient = self;
 +      self.weaponentity.flags = 0;
 +      self.weaponentity.think = CL_Weaponentity_Think;
 +      self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;
 +      self.weaponentity.nextthink = time;
 +
 +      self.exteriorweaponentity = spawn();
 +      self.exteriorweaponentity.classname = "exteriorweaponentity";
 +      self.exteriorweaponentity.solid = SOLID_NOT;
 +      self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;
 +      self.exteriorweaponentity.owner = self;
 +      setorigin(self.exteriorweaponentity, '0 0 0');
 +      self.exteriorweaponentity.angles = '0 0 0';
 +      self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;
 +      self.exteriorweaponentity.nextthink = time;
 +
 +      {
 +              entity oldself = self;
 +              self = self.exteriorweaponentity;
 +              CSQCMODEL_AUTOINIT();
 +              self = oldself;
 +      }
 +}
 +
 +// Weapon subs
 +void w_clear()
 +{
 +      if (self.weapon != -1)
 +      {
 +              self.weapon = 0;
 +              self.switchingweapon = 0;
 +      }
 +      if (self.weaponentity)
 +      {
 +              self.weaponentity.state = WS_CLEAR;
 +              self.weaponentity.effects = 0;
 +      }
 +}
 +
 +void w_ready()
 +{
 +      if (self.weaponentity)
 +              self.weaponentity.state = WS_READY;
 +      weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
 +}
 +
 +.float prevdryfire;
 +.float prevwarntime;
 +float weapon_prepareattack_checkammo(float secondary)
 +{
 +      if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
 +      if (!WEP_ACTION(self.weapon, WR_CHECKAMMO1 + secondary))
 +      {
 +              // always keep the Mine Layer if we placed mines, so that we can detonate them
 +              entity mine;
 +              if(self.weapon == WEP_MINE_LAYER)
 +              for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
 +                      return FALSE;
 +
 +              if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons
 +              {
 +                      sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTEN_NORM);
 +                      self.prevdryfire = time;
 +              }
 +
 +              if(WEP_ACTION(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo
 +              {
 +                      if(time - self.prevwarntime > 1)
 +                      {
 +                              Send_Notification(
 +                                      NOTIF_ONE,
 +                                      self,
 +                                      MSG_MULTI,
 +                                      ITEM_WEAPON_PRIMORSEC,
 +                                      self.weapon,
 +                                      secondary,
 +                                      (1 - secondary)
 +                              );
 +                      }
 +                      self.prevwarntime = time;
 +              }
 +              else // this weapon is totally unable to fire, switch to another one
 +              {
 +                      W_SwitchToOtherWeapon(self);
 +              }
 +
 +              return FALSE;
 +      }
 +      return TRUE;
 +}
 +.float race_penalty;
 +float weapon_prepareattack_check(float secondary, float attacktime)
 +{
 +      if(!weapon_prepareattack_checkammo(secondary))
 +              return FALSE;
 +
 +      //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
 +      //if all players readied up and the countdown is running
 +      if(time < game_starttime || time < self.race_penalty) {
 +              return FALSE;
 +      }
 +
 +      if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused
 +              return FALSE;
 +
 +      // do not even think about shooting if switching
 +      if(self.switchweapon != self.weapon)
 +              return FALSE;
 +
 +      if(attacktime >= 0)
 +      {
 +              // don't fire if previous attack is not finished
 +              if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)
 +                      return FALSE;
 +              // don't fire while changing weapon
 +              if (self.weaponentity.state != WS_READY)
 +                      return FALSE;
 +      }
 +
 +      return TRUE;
 +}
 +float weapon_prepareattack_do(float secondary, float attacktime)
 +{
 +      self.weaponentity.state = WS_INUSE;
 +
 +      self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
 +
 +      // if the weapon hasn't been firing continuously, reset the timer
 +      if(attacktime >= 0)
 +      {
 +              if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)
 +              {
 +                      ATTACK_FINISHED(self) = time;
 +                      //dprint("resetting attack finished to ", ftos(time), "\n");
 +              }
 +              ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();
 +      }
 +      self.bulletcounter += 1;
 +      //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
 +      return TRUE;
 +}
 +float weapon_prepareattack(float secondary, float attacktime)
 +{
 +      if(weapon_prepareattack_check(secondary, attacktime))
 +      {
 +              weapon_prepareattack_do(secondary, attacktime);
 +              return TRUE;
 +      }
 +      else
 +              return FALSE;
 +}
 +
 +void weapon_thinkf(float fr, float t, void() func)
 +{
 +      vector a;
 +      vector of, or, ou;
 +      float restartanim;
 +
 +      if(fr == WFRAME_DONTCHANGE)
 +      {
 +              fr = self.weaponentity.wframe;
 +              restartanim = FALSE;
 +      }
 +      else if (fr == WFRAME_IDLE)
 +              restartanim = FALSE;
 +      else
 +              restartanim = TRUE;
 +
 +      of = v_forward;
 +      or = v_right;
 +      ou = v_up;
 +
 +      if (self.weaponentity)
 +      {
 +              self.weaponentity.wframe = fr;
 +              a = '0 0 0';
 +              if (fr == WFRAME_IDLE)
 +                      a = self.weaponentity.anim_idle;
 +              else if (fr == WFRAME_FIRE1)
 +                      a = self.weaponentity.anim_fire1;
 +              else if (fr == WFRAME_FIRE2)
 +                      a = self.weaponentity.anim_fire2;
 +              else // if (fr == WFRAME_RELOAD)
 +                      a = self.weaponentity.anim_reload;
 +              a_z *= g_weaponratefactor;
 +              setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);
 +      }
 +
 +      v_forward = of;
 +      v_right = or;
 +      v_up = ou;
 +
 +      if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
 +      {
 +              backtrace("Tried to override initial weapon think function - should this really happen?");
 +      }
 +
 +      t *= W_WeaponRateFactor();
 +
 +      // VorteX: haste can be added here
 +      if (self.weapon_think == w_ready)
 +      {
 +              self.weapon_nextthink = time;
 +              //dprint("started firing at ", ftos(time), "\n");
 +      }
 +      if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)
 +      {
 +              self.weapon_nextthink = time;
 +              //dprint("reset weapon animation timer at ", ftos(time), "\n");
 +      }
 +      self.weapon_nextthink = self.weapon_nextthink + t;
 +      self.weapon_think = func;
 +      //dprint("next ", ftos(self.weapon_nextthink), "\n");
 +
 +      if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t)
 +      {
 +              if((self.weapon == WEP_SHOCKWAVE || self.weapon == WEP_SHOTGUN) && fr == WFRAME_FIRE2)
 +                      animdecide_setaction(self, ANIMACTION_MELEE, restartanim);
 +              else
 +                      animdecide_setaction(self, ANIMACTION_SHOOT, restartanim);
 +      }
 +      else
 +      {
 +              if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE)
 +                      self.anim_upper_action = 0;
 +      }
 +}
 +
 +float forbidWeaponUse()
 +{
 +      if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown)
 +              return 1;
 +      if(round_handler_IsActive() && !round_handler_IsRoundStarted())
 +              return 1;
 +      if(self.player_blocked)
 +              return 1;
 +      if(self.freezetag_frozen)
 +              return 1;
 +      return 0;
 +}
 +
 +void W_WeaponFrame()
 +{
 +      vector fo, ri, up;
 +
 +      if (frametime)
 +              self.weapon_frametime = frametime;
 +
 +      if (!self.weaponentity || self.health < 1)
 +              return; // Dead player can't use weapons and injure impulse commands
 +
 +      if(forbidWeaponUse())
 +      if(self.weaponentity.state != WS_CLEAR)
 +      {
 +              w_ready();
 +              return;
 +      }
 +
 +      if(!self.switchweapon)
 +      {
 +              self.weapon = 0;
 +              self.switchingweapon = 0;
 +              self.weaponentity.state = WS_CLEAR;
 +              self.weaponname = "";
 +              //self.items &= ~IT_AMMO;
 +              return;
 +      }
 +
 +      makevectors(self.v_angle);
 +      fo = v_forward; // save them in case the weapon think functions change it
 +      ri = v_right;
 +      up = v_up;
 +
 +      // Change weapon
 +      if (self.weapon != self.switchweapon)
 +      {
 +              if (self.weaponentity.state == WS_CLEAR)
 +              {
 +                      // end switching!
 +                      self.switchingweapon = self.switchweapon;
 +                      entity newwep = get_weaponinfo(self.switchweapon);
 +
 +                      //self.items &= ~IT_AMMO;
 +                      //self.items = self.items | (newwep.items & IT_AMMO);
 +
 +                      // the two weapon entities will notice this has changed and update their models
 +                      self.weapon = self.switchweapon;
 +                      self.weaponname = newwep.mdl;
 +                      self.bulletcounter = 0; // WEAPONTODO
 +                      //self.ammo_field = newwep.ammo_field;
 +                      WEP_ACTION(self.switchweapon, WR_SETUP);
 +                      self.weaponentity.state = WS_RAISE;
 +
 +                      // set our clip load to the load of the weapon we switched to, if it's reloadable
 +                      if(newwep.spawnflags & WEP_FLAG_RELOADABLE && newwep.reloading_ammo) // prevent accessing undefined cvars
 +                      {
 +                              self.clip_load = self.(weapon_load[self.switchweapon]);
 +                              self.clip_size = newwep.reloading_ammo;
 +                      }
 +                      else
 +                              self.clip_load = self.clip_size = 0;
 +
 +                      // VorteX: add player model weapon select frame here
 +                      // setcustomframe(PlayerWeaponRaise);
 +                      weapon_thinkf(WFRAME_IDLE, newwep.switchdelay_raise, w_ready);
 +                      //print(sprintf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname))));
 +              }
 +              else if (self.weaponentity.state == WS_DROP)
 +              {
 +                      // in dropping phase we can switch at any time
 +                      self.switchingweapon = self.switchweapon;
 +              }
 +              else if (self.weaponentity.state == WS_READY)
 +              {
 +                      // start switching!
 +                      self.switchingweapon = self.switchweapon;
 +
 +                      entity oldwep = get_weaponinfo(self.weapon);
 +                      
 +#ifndef INDEPENDENT_ATTACK_FINISHED
 +                      if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5)
 +                      {
 +#endif
 +                      sound (self, CH_WEAPON_SINGLE, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM);
 +                      self.weaponentity.state = WS_DROP;
 +                      // set up weapon switch think in the future, and start drop anim
 +                      weapon_thinkf(WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear);
 +                      //print(sprintf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname))));
 +#ifndef INDEPENDENT_ATTACK_FINISHED
 +                      }
 +#endif
 +              }
 +      }
 +
 +      // LordHavoc: network timing test code
 +      //if (self.button0)
 +      //      print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n");
 +
 +      float w;
 +      w = self.weapon;
 +
 +      // call the think code which may fire the weapon
 +      // and do so multiple times to resolve framerate dependency issues if the
 +      // server framerate is very low and the weapon fire rate very high
 +      float c;
 +      c = 0;
 +      while (c < W_TICSPERFRAME)
 +      {
 +              c = c + 1;
 +              if(w && !(self.weapons & WepSet_FromWeapon(w)))
 +              {
 +                      if(self.weapon == self.switchweapon)
 +                              W_SwitchWeapon_Force(self, w_getbestweapon(self));
 +                      w = 0;
 +              }
 +
 +              v_forward = fo;
 +              v_right = ri;
 +              v_up = up;
 +
 +              if(w)
 +                      WEP_ACTION(self.weapon, WR_THINK);
 +              else
 +                      WEP_ACTION(self.weapon, WR_GONETHINK);
 +
 +              if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink)
 +              {
 +                      if(self.weapon_think)
 +                      {
 +                              v_forward = fo;
 +                              v_right = ri;
 +                              v_up = up;
 +                              self.weapon_think();
 +                      }
 +                      else
 +                              bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n");
 +              }
 +      }
 +}
 +
 +void W_AttachToShotorg(entity flash, vector offset)
 +{
 +      entity xflash;
 +      flash.owner = self;
 +      flash.angles_z = random() * 360;
 +
 +      if(gettagindex(self.weaponentity, "shot"))
 +              setattachment(flash, self.weaponentity, "shot");
 +      else
 +              setattachment(flash, self.weaponentity, "tag_shot");
 +      setorigin(flash, offset);
 +
 +      xflash = spawn();
 +      copyentity(flash, xflash);
 +
 +      flash.viewmodelforclient = self;
 +
 +      if(self.weaponentity.oldorigin_x > 0)
 +      {
 +              setattachment(xflash, self.exteriorweaponentity, "");
 +              setorigin(xflash, self.weaponentity.oldorigin + offset);
 +      }
 +      else
 +      {
 +              if(gettagindex(self.exteriorweaponentity, "shot"))
 +                      setattachment(xflash, self.exteriorweaponentity, "shot");
 +              else
 +                      setattachment(xflash, self.exteriorweaponentity, "tag_shot");
 +              setorigin(xflash, offset);
 +      }
 +}
 +
 +void W_DecreaseAmmo(float ammo_use)
 +{
 +      entity wep = get_weaponinfo(self.weapon);
 +
 +      if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !wep.reloading_ammo)
 +              return;
 +
 +      // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
 +      if(wep.reloading_ammo)
 +      {
 +              self.clip_load -= ammo_use;
 +              self.(weapon_load[self.weapon]) = self.clip_load;
 +      }
 +      else
 +              self.(wep.ammo_field) -= ammo_use;
 +}
 +
 +// weapon reloading code
 +
 +.float reload_ammo_amount, reload_ammo_min, reload_time;
 +.float reload_complain;
 +.string reload_sound;
 +
 +void W_ReloadedAndReady()
 +{
 +      // finish the reloading process, and do the ammo transfer
 +
 +      self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
 +
 +      // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
 +      if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO || self.ammo_field == ammo_none)
 +              self.clip_load = self.reload_ammo_amount;
 +      else
 +      {
 +              while(self.clip_load < self.reload_ammo_amount && self.(self.ammo_field)) // make sure we don't add more ammo than we have
 +              {
 +                      self.clip_load += 1;
 +                      self.(self.ammo_field) -= 1;
 +              }
 +      }
 +      self.(weapon_load[self.weapon]) = self.clip_load;
 +
 +      // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
 +      // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
 +      // so your weapon is disabled for a few seconds without reason
 +
 +      //ATTACK_FINISHED(self) -= self.reload_time - 1;
 +
 +      w_ready();
 +}
 +
 +void W_Reload(float sent_ammo_min, string sent_sound)
 +{
 +      // set global values to work with
 +      entity e;
 +      e = get_weaponinfo(self.weapon);
 +
 +      self.reload_ammo_min = sent_ammo_min;
 +      self.reload_ammo_amount = e.reloading_ammo;;
 +      self.reload_time = e.reloading_time;
 +      self.reload_sound = sent_sound;
 +
 +      // don't reload weapons that don't have the RELOADABLE flag
 +      if (!(e.spawnflags & WEP_FLAG_RELOADABLE))
 +      {
 +              dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");
 +              return;
 +      }
 +
 +      // return if reloading is disabled for this weapon
 +      if(!self.reload_ammo_amount)
 +              return;
 +
 +      // our weapon is fully loaded, no need to reload
 +      if (self.clip_load >= self.reload_ammo_amount)
 +              return;
 +
 +      // no ammo, so nothing to load
 +      if(self.ammo_field != ammo_none)
 +      if(!self.(self.ammo_field) && self.reload_ammo_min)
 +      if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
 +      {
 +              if(IS_REAL_CLIENT(self) && self.reload_complain < time)
 +              {
 +                      play2(self, "weapons/unavailable.wav");
 +                      sprint(self, strcat("You don't have enough ammo to reload the ^2", W_Name(self.weapon), "\n"));
 +                      self.reload_complain = time + 1;
 +              }
 +              // switch away if the amount of ammo is not enough to keep using this weapon
 +              if (!(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2)))
 +              {
 +                      self.clip_load = -1; // reload later
 +                      W_SwitchToOtherWeapon(self);
 +              }
 +              return;
 +      }
 +
 +      if (self.weaponentity)
 +      {
 +              if (self.weaponentity.wframe == WFRAME_RELOAD)
 +                      return;
 +
 +              // allow switching away while reloading, but this will cause a new reload!
 +              self.weaponentity.state = WS_READY;
 +      }
 +
 +      // now begin the reloading process
 +
 +      sound(self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTEN_NORM);
 +
 +      // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
 +      // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
 +      // so your weapon is disabled for a few seconds without reason
 +
 +      //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;
 +
 +      weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);
 +
 +      if(self.clip_load < 0)
 +              self.clip_load = 0;
 +      self.old_clip_load = self.clip_load;
 +      self.clip_load = self.(weapon_load[self.weapon]) = -1;
 +}