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/net_linked.qh>
14 #include <common/util.qh>
16 #include <common/weapons/_all.qh>
17 #include <common/state.qh>
19 #include <lib/warpzone/common.qh>
21 // this function calculates w_shotorg and w_shotdir based on the weapon model
22 // offset, trueaim and antilag, and won't put w_shotorg inside a wall.
23 // make sure you call makevectors first (FIXME?)
24 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)
27 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?
30 oldsolid = ent.dphitcontentsmask;
31 if (IS_PLAYER(ent) && ent.(weaponentity).m_weapon == WEP_RIFLE)
32 ent.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_CORPSE;
34 ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
36 WarpZone_traceline_antilag(NULL, ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
37 // passing NULL, because we do NOT want it to touch dphitcontentsmask
39 WarpZone_TraceLine(ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NOMONSTERS, ent);
40 ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
46 w_shotend = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); // warpzone support
51 // un-adjust trueaim if shotend is too close
52 if(vdist(w_shotend - (ent.origin + ent.view_ofs), <, autocvar_g_trueaim_minrange))
53 w_shotend = ent.origin + ent.view_ofs + s_forward * autocvar_g_trueaim_minrange;
56 if (IS_PLAYER(ent) && accuracy_canbegooddamage(ent))
57 accuracy_add(ent, ent.(weaponentity).m_weapon.m_id, maxdamage, 0);
60 W_HitPlotAnalysis(ent, weaponentity, v_forward, v_right, v_up);
62 vector md = ent.(weaponentity).movedir;
68 dv = v_right * -vecs.y + v_up * vecs.z;
69 w_shotorg = ent.origin + ent.view_ofs + dv;
71 // now move the shotorg forward as much as requested if possible
74 if(CS(ent).antilag_debug)
75 tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent, CS(ent).antilag_debug);
77 tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
80 tracebox(w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent);
81 w_shotorg = trace_endpos - v_forward * nudge;
82 // calculate the shotdir from the chosen shotorg
83 w_shotdir = normalize(w_shotend - w_shotorg);
85 //vector prevdir = w_shotdir;
86 //vector prevorg = w_shotorg;
87 //vector prevend = w_shotend;
90 if (!ent.cvar_cl_noantilag)
92 if (autocvar_g_antilag == 1) // switch to "ghost" if not hitting original
94 traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
95 if (!trace_ent.takedamage)
97 traceline_antilag_force (ent, w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
98 if (trace_ent.takedamage && IS_PLAYER(trace_ent))
102 traceline(w_shotorg, e.origin, MOVE_NORMAL, ent);
104 w_shotdir = normalize(trace_ent.origin - w_shotorg);
108 else if(autocvar_g_antilag == 3) // client side hitscan
110 // this part MUST use prydon cursor
111 if (ent.cursor_trace_ent) // client was aiming at someone
112 if (ent.cursor_trace_ent != ent) // just to make sure
113 if (ent.cursor_trace_ent.takedamage) // and that person is killable
114 if (IS_PLAYER(ent.cursor_trace_ent)) // and actually a player
116 // verify that the shot would miss without antilag
117 // (avoids an issue where guns would always shoot at their origin)
118 traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
119 if (!trace_ent.takedamage)
121 // verify that the shot would hit if altered
122 traceline(w_shotorg, ent.cursor_trace_ent.origin, MOVE_NORMAL, ent);
123 if (trace_ent == ent.cursor_trace_ent)
124 w_shotdir = normalize(ent.cursor_trace_ent.origin - w_shotorg);
126 LOG_INFO("antilag fail\n");
132 ent.dphitcontentsmask = oldsolid; // restore solid type (generally SOLID_SLIDEBOX)
134 if (!autocvar_g_norecoil)
135 ent.punchangle_x = recoil * -1;
137 if (snd != SND_Null) {
138 sound (ent, chan, snd, VOL_BASE, ATTN_NORM);
139 W_PlayStrengthSound(ent);
142 // nudge w_shotend so a trace to w_shotend hits
143 w_shotend = w_shotend + normalize(w_shotend - w_shotorg) * nudge;
144 //if(w_shotend != prevend) { printf("SERVER: shotEND differs: %s - %s\n", vtos(w_shotend), vtos(prevend)); }
145 //if(w_shotorg != prevorg) { printf("SERVER: shotORG differs: %s - %s\n", vtos(w_shotorg), vtos(prevorg)); }
146 //if(w_shotdir != prevdir) { printf("SERVER: shotDIR differs: %s - %s\n", vtos(w_shotdir), vtos(prevdir)); }
149 vector W_CalculateProjectileVelocity(entity actor, vector pvelocity, vector mvelocity, float forceAbsolute)
155 mvelocity = mvelocity * W_WeaponSpeedFactor(actor);
157 mdirection = normalize(mvelocity);
158 mspeed = vlen(mvelocity);
160 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);
165 void W_SetupProjVelocity_Explicit(entity proj, vector dir, vector upDir, float pSpeed, float pUpSpeed, float pZSpeed, float spread, float forceAbsolute)
167 if(proj.owner == NULL)
168 error("Unowned missile");
170 dir = dir + upDir * (pUpSpeed / pSpeed);
171 dir.z += pZSpeed / pSpeed;
173 dir = normalize(dir);
176 if(autocvar_g_projectiles_spread_style != mspercallsstyle)
178 mspercallsum = mspercallcount = 0;
179 mspercallsstyle = autocvar_g_projectiles_spread_style;
181 mspercallsum -= gettime(GETTIME_HIRES);
184 dir = W_CalculateSpread(dir, spread, g_weaponspreadfactor, autocvar_g_projectiles_spread_style);
187 mspercallsum += gettime(GETTIME_HIRES);
189 LOG_INFO("avg: ", ftos(mspercallcount / mspercallsum), " per sec\n");
192 proj.velocity = W_CalculateProjectileVelocity(proj.owner, proj.owner.velocity, pSpeed * dir, forceAbsolute);
196 // ====================
197 // Ballistics Tracing
198 // ====================
200 void FireRailgunBullet (entity this, .entity weaponentity, vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, int deathtype)
202 vector hitloc, force, endpoint, dir;
204 float endq3surfaceflags;
211 entity pseudoprojectile;
214 pseudoprojectile = NULL;
216 dir = normalize(end - start);
217 length = vlen(end - start);
218 force = dir * bforce;
220 // go a little bit into the wall because we need to hit this wall later
225 // trace multiple times until we hit a wall, each obstacle will be made
226 // non-solid so we can hit the next, while doing this we spawn effects and
227 // note down which entities were hit so we can damage them later
231 if(CS(this).antilag_debug)
232 WarpZone_traceline_antilag (this, start, end, false, o, CS(this).antilag_debug);
234 WarpZone_traceline_antilag (this, start, end, false, o, ANTILAG_LATENCY(this));
235 if(o && WarpZone_trace_firstzone)
241 if(trace_ent.solid == SOLID_BSP || trace_ent.solid == SOLID_SLIDEBOX)
242 Damage_DamageInfo(trace_endpos, bdamage, 0, 0, force, deathtype, trace_ent.species, this);
244 // if it is NULL we can't hurt it so stop now
245 if (trace_ent == NULL || trace_fraction == 1)
248 // make the entity non-solid so we can hit the next one
249 trace_ent.railgunhit = true;
250 trace_ent.railgunhitloc = end;
251 trace_ent.railgunhitsolidbackup = trace_ent.solid;
252 trace_ent.railgundistance = vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - start);
253 trace_ent.railgunforce = WarpZone_TransformVelocity(WarpZone_trace_transform, force);
255 // stop if this is a wall
256 if (trace_ent.solid == SOLID_BSP)
259 // make the entity non-solid
260 trace_ent.solid = SOLID_NOT;
263 endpoint = trace_endpos;
265 endq3surfaceflags = trace_dphitq3surfaceflags;
267 // find all the entities the railgun hit and restore their solid state
268 ent = findfloat(NULL, railgunhit, true);
271 // restore their solid type
272 ent.solid = ent.railgunhitsolidbackup;
273 ent = findfloat(ent, railgunhit, true);
276 // spawn a temporary explosion entity for RadiusDamage calls
277 //explosion = spawn();
279 // Find all non-hit players the beam passed close by
280 if(deathtype == WEP_VAPORIZER.m_id || deathtype == WEP_VORTEX.m_id)
282 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != this, LAMBDA(
284 if(!(IS_SPEC(it) && it.enemy == this))
287 // nearest point on the beam
288 beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
290 f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
294 snd = SND(NEXWHOOSH_RANDOM());
296 if(!pseudoprojectile)
297 pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
298 soundtoat(MSG_ONE, pseudoprojectile, beampos, CH_SHOTS, snd, VOL_BASE * f, ATTEN_NONE);
303 delete(pseudoprojectile);
306 // find all the entities the railgun hit and hurt them
307 ent = findfloat(NULL, railgunhit, true);
310 // get the details we need to call the damage function
311 hitloc = ent.railgunhitloc;
313 f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance);
314 ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance);
316 if(accuracy_isgooddamage(this, ent))
317 totaldmg += bdamage * f;
321 Damage (ent, this, this, bdamage * f, deathtype, hitloc, ent.railgunforce * ffs);
323 // create a small explosion to throw gibs around (if applicable)
324 //setorigin(explosion, hitloc);
325 //RadiusDamage (explosion, this, 10, 0, 50, NULL, NULL, 300, deathtype);
327 ent.railgunhitloc = '0 0 0';
328 ent.railgunhitsolidbackup = SOLID_NOT;
329 ent.railgunhit = false;
330 ent.railgundistance = 0;
332 // advance to the next entity
333 ent = findfloat(ent, railgunhit, true);
336 // calculate hits and fired shots for hitscan
337 accuracy_add(this, this.(weaponentity).m_weapon.m_id, 0, min(bdamage, totaldmg));
339 trace_endpos = endpoint;
341 trace_dphitq3surfaceflags = endq3surfaceflags;
344 void fireBullet_trace_callback(vector start, vector hit, vector end)
346 if(vdist(hit - start, >, 16))
347 trailparticles(NULL, fireBullet_trace_callback_eff, start, hit);
348 WarpZone_trace_forent = NULL;
349 fireBullet_last_hit = NULL;
352 void fireBullet(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, int tracereffects)
356 dir = normalize(dir + randomvec() * spread);
357 end = start + dir * MAX_SHOT_DISTANCE;
359 fireBullet_last_hit = NULL;
360 float solid_penetration_left = 1;
361 float total_damage = 0;
363 if(tracereffects & EF_RED)
364 fireBullet_trace_callback_eff = EFFECT_RIFLE;
365 else if(tracereffects & EF_BLUE)
366 fireBullet_trace_callback_eff = EFFECT_RIFLE_WEAK;
368 fireBullet_trace_callback_eff = EFFECT_BULLET;
370 float lag = ANTILAG_LATENCY(this);
373 if (!IS_REAL_CLIENT(this))
375 if(autocvar_g_antilag == 0 || this.cvar_cl_noantilag)
376 lag = 0; // only do hitscan, but no antilag
379 FOREACH_CLIENT(IS_PLAYER(it) && it != this, antilag_takeback(it, CS(it), time - lag));
380 IL_EACH(g_monsters, it != this,
382 antilag_takeback(it, it, time - lag);
386 // change shooter to SOLID_BBOX so the shot can hit corpses
387 int oldsolid = this.dphitcontentsmask;
389 this.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
391 WarpZone_trace_forent = this;
395 // TODO also show effect while tracing
396 WarpZone_TraceBox_ThroughZone(start, '0 0 0', '0 0 0', end, false, WarpZone_trace_forent, NULL, fireBullet_trace_callback);
397 dir = WarpZone_TransformVelocity(WarpZone_trace_transform, dir);
398 end = WarpZone_TransformOrigin(WarpZone_trace_transform, end);
399 start = trace_endpos;
400 entity hit = trace_ent;
402 // When hitting sky, stop.
403 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
406 // can't use noimpact, as we need to pass through walls
407 //if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
410 // if we hit "weapclip", bail out
412 // rationale of this check:
414 // any shader that is solid, nodraw AND trans is meant to clip weapon
415 // shots and players, but has no other effect!
417 // if it is not trans, it is caulk and should not have this side effect
420 // common/weapclip (intended)
421 // common/noimpact (is supposed to eat projectiles, but is erased anyway)
422 bool is_weapclip = false;
423 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
424 if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
425 if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
428 if(!hit || hit.solid == SOLID_BSP || hit.solid == SOLID_SLIDEBOX)
429 Damage_DamageInfo(start, damage * solid_penetration_left, 0, 0, max(1, force) * dir * solid_penetration_left, dtype, hit.species, this);
431 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).
433 fireBullet_last_hit = hit;
435 MUTATOR_CALLHOOK(FireBullet_Hit, this, hit, start, end, damage);
436 damage = M_ARGV(4, float);
437 float g = accuracy_isgooddamage(this, hit);
438 Damage(hit, this, this, damage * solid_penetration_left, dtype, start, force * dir * solid_penetration_left);
439 // calculate hits for ballistic weapons
442 // do not exceed 100%
443 float added_damage = min(damage - total_damage, damage * solid_penetration_left);
444 total_damage += damage * solid_penetration_left;
445 accuracy_add(this, this.(weaponentity).m_weapon.m_id, 0, added_damage);
449 if (is_weapclip && !autocvar_g_ballistics_penetrate_clips)
453 // outside the world? forget it
454 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)
458 if(max_solid_penetration < 0)
460 else if(hit.ballistics_density < -1)
461 break; // -2: no solid penetration, ever
462 else if(hit.ballistics_density < 0)
463 maxdist = vlen(hit.maxs - hit.mins) + 1; // -1: infinite travel distance
464 else if(hit.ballistics_density == 0)
465 maxdist = max_solid_penetration * solid_penetration_left;
467 maxdist = max_solid_penetration * solid_penetration_left * hit.ballistics_density;
469 if(maxdist <= autocvar_g_ballistics_mindistance)
472 // move the entity along its velocity until it's out of solid, then let it resume
473 // The previously hit entity is ignored here!
474 traceline_inverted (start, start + dir * maxdist, MOVE_NORMAL, WarpZone_trace_forent, true, hit);
475 if(trace_fraction == 1) // 1: we never got out of solid
478 float dist_taken = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - start));
479 // fraction_used_of_what_is_left = dist_taken / maxdist
480 // solid_penetration_left = solid_penetration_left - solid_penetration_left * fraction_used_of_what_is_left
481 solid_penetration_left *= 1 - dist_taken / maxdist;
482 solid_penetration_left = max(solid_penetration_left, 0);
484 // Only show effect when going through a player (invisible otherwise)
485 if (hit && (hit.solid != SOLID_BSP))
486 if(vdist(trace_endpos - start, >, 4))
487 trailparticles(this, fireBullet_trace_callback_eff, start, trace_endpos);
489 start = trace_endpos;
491 if(hit.solid == SOLID_BSP)
492 Damage_DamageInfo(start, 0, 0, 0, max(1, force) * normalize(dir) * -solid_penetration_left, dtype, 0, this);
497 FOREACH_CLIENT(IS_PLAYER(it) && it != this, antilag_restore(it, CS(it)));
498 IL_EACH(g_monsters, it != this,
500 antilag_restore(it, it);
504 // restore shooter solid type
506 this.dphitcontentsmask = oldsolid;