From: Samual Lenks Date: Wed, 1 Jan 2014 13:11:17 +0000 (-0500) Subject: Merge remote-tracking branch 'origin/master' into samual/weapons X-Git-Tag: xonotic-v0.8.0~152^2~178 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=ee3543c1c14ffc6cd14b6dbd07d76d4cb65e3bd6;p=xonotic%2Fxonotic-data.pk3dir.git Merge remote-tracking branch 'origin/master' into samual/weapons 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 --- ee3543c1c14ffc6cd14b6dbd07d76d4cb65e3bd6 diff --cc qcsrc/client/miscfunctions.qc index 569073694,61e527cf1..9b42da8aa --- a/qcsrc/client/miscfunctions.qc +++ b/qcsrc/client/miscfunctions.qc @@@ -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; diff --cc qcsrc/common/weapons/calculations.qc index 183a5e5ed,000000000..7bc64e0b5 mode 100644,000000..100644 --- a/qcsrc/common/weapons/calculations.qc +++ b/qcsrc/common/weapons/calculations.qc @@@ -1,261 -1,0 +1,261 @@@ +// ============================= +// 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 - print(sprintf("MASS: %f\nv: %v -> %v\nENERGY BEFORE == %f + %f = %f\nENERGY AFTER >= %f\n", ++ 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))); ++ (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; + */ +} diff --cc qcsrc/common/weapons/w_tuba.qc index 8adbcd024,000000000..9c8ea1a20 mode 100644,000000..100644 --- a/qcsrc/common/weapons/w_tuba.qc +++ b/qcsrc/common/weapons/w_tuba.qc @@@ -1,504 -1,0 +1,504 @@@ +#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("initial tempo rules: %f %f\n", mmin, mmax)); ++ //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 - //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("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 diff --cc qcsrc/server/g_damage.qc index b9d2ab9aa,2ee79c407..96e4d0af1 --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@@ -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); } } } diff --cc qcsrc/server/weapons/accuracy.qc index 00605ce6b,000000000..5b22669ce mode 100644,000000..100644 --- a/qcsrc/server/weapons/accuracy.qc +++ b/qcsrc/server/weapons/accuracy.qc @@@ -1,120 -1,0 +1,120 @@@ +float accuracy_byte(float n, float d) +{ - //print(sprintf("accuracy: %d / %d\n", n, 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; +} diff --cc qcsrc/server/weapons/weaponsystem.qc index da98d7f1c,000000000..9ac6202fd mode 100644,000000..100644 --- a/qcsrc/server/weapons/weaponsystem.qc +++ b/qcsrc/server/weapons/weaponsystem.qc @@@ -1,934 -1,0 +1,932 @@@ +/* +=========================================================================== + + 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_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.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); - //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_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; +}