6 #include "weaponsystem.qh"
8 #include "../g_damage.qh"
9 #include "../g_subs.qh"
10 #include "../antilag.qh"
12 #include <common/constants.qh>
13 #include <common/util.qh>
15 #include <common/weapons/_all.qh>
16 #include <common/state.qh>
18 #include <lib/warpzone/common.qh>
20 // this function calculates w_shotorg and w_shotdir based on the weapon model
21 // offset, trueaim and antilag, and won't put w_shotorg inside a wall.
22 // make sure you call makevectors first (FIXME?)
23 void W_SetupShot_Dir_ProjectileSize_Range(entity ent, .entity weaponentity, vector s_forward, vector mi, vector ma, float antilag, float recoil, Sound snd, float chan, float maxdamage, float range)
26 float nudge = 1; // added to traceline target and subtracted from result TOOD(divVerent): do we still need this? Doesn't the engine do this now for us?
29 oldsolid = ent.dphitcontentsmask;
30 if (IS_PLAYER(ent) && ent.(weaponentity).m_weapon == WEP_RIFLE)
31 ent.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_CORPSE;
33 ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
35 WarpZone_traceline_antilag(NULL, ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
36 // passing NULL, because we do NOT want it to touch dphitcontentsmask
38 WarpZone_TraceLine(ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NOMONSTERS, ent);
39 ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
45 w_shotend = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); // warpzone support
50 // un-adjust trueaim if shotend is too close
51 if(vdist(w_shotend - (ent.origin + ent.view_ofs), <, autocvar_g_trueaim_minrange))
52 w_shotend = ent.origin + ent.view_ofs + s_forward * autocvar_g_trueaim_minrange;
55 if (IS_PLAYER(ent) && accuracy_canbegooddamage(ent))
56 accuracy_add(ent, ent.(weaponentity).m_weapon.m_id, maxdamage, 0);
59 W_HitPlotAnalysis(ent, v_forward, v_right, v_up);
61 vector md = ent.(weaponentity).movedir;
67 dv = v_right * -vecs.y + v_up * vecs.z;
68 w_shotorg = ent.origin + ent.view_ofs + dv;
70 // now move the shotorg forward as much as requested if possible
73 if(CS(ent).antilag_debug)
74 tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent, CS(ent).antilag_debug);
76 tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
79 tracebox(w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent);
80 w_shotorg = trace_endpos - v_forward * nudge;
81 // calculate the shotdir from the chosen shotorg
82 w_shotdir = normalize(w_shotend - w_shotorg);
84 //vector prevdir = w_shotdir;
85 //vector prevorg = w_shotorg;
86 //vector prevend = w_shotend;
89 if (!ent.cvar_cl_noantilag)
91 if (autocvar_g_antilag == 1) // switch to "ghost" if not hitting original
93 traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
94 if (!trace_ent.takedamage)
96 traceline_antilag_force (ent, w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
97 if (trace_ent.takedamage && IS_PLAYER(trace_ent))
101 traceline(w_shotorg, e.origin, MOVE_NORMAL, ent);
103 w_shotdir = normalize(trace_ent.origin - w_shotorg);
107 else if(autocvar_g_antilag == 3) // client side hitscan
109 // this part MUST use prydon cursor
110 if (ent.cursor_trace_ent) // client was aiming at someone
111 if (ent.cursor_trace_ent != ent) // just to make sure
112 if (ent.cursor_trace_ent.takedamage) // and that person is killable
113 if (IS_PLAYER(ent.cursor_trace_ent)) // and actually a player
115 // verify that the shot would miss without antilag
116 // (avoids an issue where guns would always shoot at their origin)
117 traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
118 if (!trace_ent.takedamage)
120 // verify that the shot would hit if altered
121 traceline(w_shotorg, ent.cursor_trace_ent.origin, MOVE_NORMAL, ent);
122 if (trace_ent == ent.cursor_trace_ent)
123 w_shotdir = normalize(ent.cursor_trace_ent.origin - w_shotorg);
125 LOG_INFO("antilag fail\n");
131 ent.dphitcontentsmask = oldsolid; // restore solid type (generally SOLID_SLIDEBOX)
133 if (!autocvar_g_norecoil)
134 ent.punchangle_x = recoil * -1;
136 if (snd != SND_Null) {
137 sound (ent, chan, snd, VOL_BASE, ATTN_NORM);
138 W_PlayStrengthSound(ent);
141 // nudge w_shotend so a trace to w_shotend hits
142 w_shotend = w_shotend + normalize(w_shotend - w_shotorg) * nudge;
143 //if(w_shotend != prevend) { printf("SERVER: shotEND differs: %s - %s\n", vtos(w_shotend), vtos(prevend)); }
144 //if(w_shotorg != prevorg) { printf("SERVER: shotORG differs: %s - %s\n", vtos(w_shotorg), vtos(prevorg)); }
145 //if(w_shotdir != prevdir) { printf("SERVER: shotDIR differs: %s - %s\n", vtos(w_shotdir), vtos(prevdir)); }
148 vector W_CalculateProjectileVelocity(entity actor, vector pvelocity, vector mvelocity, float forceAbsolute)
154 mvelocity = mvelocity * W_WeaponSpeedFactor(actor);
156 mdirection = normalize(mvelocity);
157 mspeed = vlen(mvelocity);
159 outvelocity = get_shotvelocity(pvelocity, mdirection, mspeed, (forceAbsolute ? 0 : autocvar_g_projectiles_newton_style), autocvar_g_projectiles_newton_style_2_minfactor, autocvar_g_projectiles_newton_style_2_maxfactor);
164 void W_SetupProjVelocity_Explicit(entity proj, vector dir, vector upDir, float pSpeed, float pUpSpeed, float pZSpeed, float spread, float forceAbsolute)
166 if(proj.owner == NULL)
167 error("Unowned missile");
169 dir = dir + upDir * (pUpSpeed / pSpeed);
170 dir.z += pZSpeed / pSpeed;
172 dir = normalize(dir);
175 if(autocvar_g_projectiles_spread_style != mspercallsstyle)
177 mspercallsum = mspercallcount = 0;
178 mspercallsstyle = autocvar_g_projectiles_spread_style;
180 mspercallsum -= gettime(GETTIME_HIRES);
183 dir = W_CalculateSpread(dir, spread, g_weaponspreadfactor, autocvar_g_projectiles_spread_style);
186 mspercallsum += gettime(GETTIME_HIRES);
188 LOG_INFO("avg: ", ftos(mspercallcount / mspercallsum), " per sec\n");
191 proj.velocity = W_CalculateProjectileVelocity(proj.owner, proj.owner.velocity, pSpeed * dir, forceAbsolute);
195 // ====================
196 // Ballistics Tracing
197 // ====================
199 void FireRailgunBullet (entity this, .entity weaponentity, vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, int deathtype)
201 vector hitloc, force, endpoint, dir;
203 float endq3surfaceflags;
210 entity pseudoprojectile;
213 pseudoprojectile = NULL;
215 dir = normalize(end - start);
216 length = vlen(end - start);
217 force = dir * bforce;
219 // go a little bit into the wall because we need to hit this wall later
224 // trace multiple times until we hit a wall, each obstacle will be made
225 // non-solid so we can hit the next, while doing this we spawn effects and
226 // note down which entities were hit so we can damage them later
230 if(CS(this).antilag_debug)
231 WarpZone_traceline_antilag (this, start, end, false, o, CS(this).antilag_debug);
233 WarpZone_traceline_antilag (this, start, end, false, o, ANTILAG_LATENCY(this));
234 if(o && WarpZone_trace_firstzone)
240 if(trace_ent.solid == SOLID_BSP || trace_ent.solid == SOLID_SLIDEBOX)
241 Damage_DamageInfo(trace_endpos, bdamage, 0, 0, force, deathtype, trace_ent.species, this);
243 // if it is NULL we can't hurt it so stop now
244 if (trace_ent == NULL || trace_fraction == 1)
247 // make the entity non-solid so we can hit the next one
248 trace_ent.railgunhit = true;
249 trace_ent.railgunhitloc = end;
250 trace_ent.railgunhitsolidbackup = trace_ent.solid;
251 trace_ent.railgundistance = vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - start);
252 trace_ent.railgunforce = WarpZone_TransformVelocity(WarpZone_trace_transform, force);
254 // stop if this is a wall
255 if (trace_ent.solid == SOLID_BSP)
258 // make the entity non-solid
259 trace_ent.solid = SOLID_NOT;
262 endpoint = trace_endpos;
264 endq3surfaceflags = trace_dphitq3surfaceflags;
266 // find all the entities the railgun hit and restore their solid state
267 ent = findfloat(NULL, railgunhit, true);
270 // restore their solid type
271 ent.solid = ent.railgunhitsolidbackup;
272 ent = findfloat(ent, railgunhit, true);
275 // spawn a temporary explosion entity for RadiusDamage calls
276 //explosion = spawn();
278 // Find all non-hit players the beam passed close by
279 if(deathtype == WEP_VAPORIZER.m_id || deathtype == WEP_VORTEX.m_id)
281 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != this, LAMBDA(
283 if(!(IS_SPEC(it) && it.enemy == this))
286 // nearest point on the beam
287 beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
289 f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
293 snd = SND(NEXWHOOSH_RANDOM());
295 if(!pseudoprojectile)
296 pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
297 soundtoat(MSG_ONE, pseudoprojectile, beampos, CH_SHOTS, snd, VOL_BASE * f, ATTEN_NONE);
302 delete(pseudoprojectile);
305 // find all the entities the railgun hit and hurt them
306 ent = findfloat(NULL, railgunhit, true);
309 // get the details we need to call the damage function
310 hitloc = ent.railgunhitloc;
312 f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance);
313 ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance);
315 if(accuracy_isgooddamage(this, ent))
316 totaldmg += bdamage * f;
320 Damage (ent, this, this, bdamage * f, deathtype, hitloc, ent.railgunforce * ffs);
322 // create a small explosion to throw gibs around (if applicable)
323 //setorigin(explosion, hitloc);
324 //RadiusDamage (explosion, this, 10, 0, 50, NULL, NULL, 300, deathtype);
326 ent.railgunhitloc = '0 0 0';
327 ent.railgunhitsolidbackup = SOLID_NOT;
328 ent.railgunhit = false;
329 ent.railgundistance = 0;
331 // advance to the next entity
332 ent = findfloat(ent, railgunhit, true);
335 // calculate hits and fired shots for hitscan
336 accuracy_add(this, this.(weaponentity).m_weapon.m_id, 0, min(bdamage, totaldmg));
338 trace_endpos = endpoint;
340 trace_dphitq3surfaceflags = endq3surfaceflags;
343 void fireBullet_trace_callback(vector start, vector hit, vector end)
345 if(vdist(hit - start, >, 16))
346 trailparticles(NULL, fireBullet_trace_callback_eff, start, hit);
347 WarpZone_trace_forent = NULL;
348 fireBullet_last_hit = NULL;
351 void fireBullet(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, int tracereffects)
355 dir = normalize(dir + randomvec() * spread);
356 end = start + dir * MAX_SHOT_DISTANCE;
358 fireBullet_last_hit = NULL;
359 float solid_penetration_left = 1;
360 float total_damage = 0;
362 if(tracereffects & EF_RED)
363 fireBullet_trace_callback_eff = EFFECT_RIFLE;
364 else if(tracereffects & EF_BLUE)
365 fireBullet_trace_callback_eff = EFFECT_RIFLE_WEAK;
367 fireBullet_trace_callback_eff = EFFECT_BULLET;
369 float lag = ANTILAG_LATENCY(this);
372 if (!IS_REAL_CLIENT(this))
374 if(autocvar_g_antilag == 0 || this.cvar_cl_noantilag)
375 lag = 0; // only do hitscan, but no antilag
378 FOREACH_CLIENT(IS_PLAYER(it) && it != this, antilag_takeback(it, CS(it), time - lag));
379 IL_EACH(g_monsters, it != this,
381 antilag_takeback(it, it, time - lag);
385 // change shooter to SOLID_BBOX so the shot can hit corpses
386 int oldsolid = this.dphitcontentsmask;
388 this.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
390 WarpZone_trace_forent = this;
394 // TODO also show effect while tracing
395 WarpZone_TraceBox_ThroughZone(start, '0 0 0', '0 0 0', end, false, WarpZone_trace_forent, NULL, fireBullet_trace_callback);
396 dir = WarpZone_TransformVelocity(WarpZone_trace_transform, dir);
397 end = WarpZone_TransformOrigin(WarpZone_trace_transform, end);
398 start = trace_endpos;
399 entity hit = trace_ent;
401 // When hitting sky, stop.
402 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
405 // can't use noimpact, as we need to pass through walls
406 //if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
409 // if we hit "weapclip", bail out
411 // rationale of this check:
413 // any shader that is solid, nodraw AND trans is meant to clip weapon
414 // shots and players, but has no other effect!
416 // if it is not trans, it is caulk and should not have this side effect
419 // common/weapclip (intended)
420 // common/noimpact (is supposed to eat projectiles, but is erased anyway)
421 bool is_weapclip = false;
422 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
423 if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
424 if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
427 if(!hit || hit.solid == SOLID_BSP || hit.solid == SOLID_SLIDEBOX)
428 Damage_DamageInfo(start, damage * solid_penetration_left, 0, 0, max(1, force) * dir * solid_penetration_left, dtype, hit.species, this);
430 if (hit && hit != WarpZone_trace_forent && hit != fireBullet_last_hit) // Avoid self-damage (except after going through a warp); avoid hitting the same entity twice (engine bug).
432 fireBullet_last_hit = hit;
434 MUTATOR_CALLHOOK(FireBullet_Hit, this, hit, start, end, damage);
435 damage = M_ARGV(4, float);
436 float g = accuracy_isgooddamage(this, hit);
437 Damage(hit, this, this, damage * solid_penetration_left, dtype, start, force * dir * solid_penetration_left);
438 // calculate hits for ballistic weapons
441 // do not exceed 100%
442 float added_damage = min(damage - total_damage, damage * solid_penetration_left);
443 total_damage += damage * solid_penetration_left;
444 accuracy_add(this, this.(weaponentity).m_weapon.m_id, 0, added_damage);
448 if (is_weapclip && !autocvar_g_ballistics_penetrate_clips)
452 // outside the world? forget it
453 if(start.x > world.maxs.x || start.y > world.maxs.y || start.z > world.maxs.z || start.x < world.mins.x || start.y < world.mins.y || start.z < world.mins.z)
457 if(max_solid_penetration < 0)
459 else if(hit.ballistics_density < -1)
460 break; // -2: no solid penetration, ever
461 else if(hit.ballistics_density < 0)
462 maxdist = vlen(hit.maxs - hit.mins) + 1; // -1: infinite travel distance
463 else if(hit.ballistics_density == 0)
464 maxdist = max_solid_penetration * solid_penetration_left;
466 maxdist = max_solid_penetration * solid_penetration_left * hit.ballistics_density;
468 if(maxdist <= autocvar_g_ballistics_mindistance)
471 // move the entity along its velocity until it's out of solid, then let it resume
472 // The previously hit entity is ignored here!
473 traceline_inverted (start, start + dir * maxdist, MOVE_NORMAL, WarpZone_trace_forent, true, hit);
474 if(trace_fraction == 1) // 1: we never got out of solid
477 float dist_taken = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - start));
478 // fraction_used_of_what_is_left = dist_taken / maxdist
479 // solid_penetration_left = solid_penetration_left - solid_penetration_left * fraction_used_of_what_is_left
480 solid_penetration_left *= 1 - dist_taken / maxdist;
481 solid_penetration_left = max(solid_penetration_left, 0);
483 // Only show effect when going through a player (invisible otherwise)
484 if (hit && (hit.solid != SOLID_BSP))
485 if(vdist(trace_endpos - start, >, 4))
486 trailparticles(this, fireBullet_trace_callback_eff, start, trace_endpos);
488 start = trace_endpos;
490 if(hit.solid == SOLID_BSP)
491 Damage_DamageInfo(start, 0, 0, 0, max(1, force) * normalize(dir) * -solid_penetration_left, dtype, 0, this);
496 FOREACH_CLIENT(IS_PLAYER(it) && it != this, antilag_restore(it, CS(it)));
497 IL_EACH(g_monsters, it != this,
499 antilag_restore(it, it);
503 // restore shooter solid type
505 this.dphitcontentsmask = oldsolid;