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 FOREACH_ENTITY_FLOAT(railgunhit, true,
270 it.solid = it.railgunhitsolidbackup;
273 // spawn a temporary explosion entity for RadiusDamage calls
274 //explosion = spawn();
276 // Find all non-hit players the beam passed close by
277 if(deathtype == WEP_VAPORIZER.m_id || deathtype == WEP_VORTEX.m_id)
279 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != this, LAMBDA(
281 if(!(IS_SPEC(it) && it.enemy == this))
284 // nearest point on the beam
285 beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
287 f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
291 snd = SND(NEXWHOOSH_RANDOM());
293 if(!pseudoprojectile)
294 pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
295 soundtoat(MSG_ONE, pseudoprojectile, beampos, CH_SHOTS, snd, VOL_BASE * f, ATTEN_NONE);
300 delete(pseudoprojectile);
303 // find all the entities the railgun hit and hurt them
304 FOREACH_ENTITY_FLOAT(railgunhit, true,
306 // get the details we need to call the damage function
307 hitloc = it.railgunhitloc;
309 f = ExponentialFalloff(mindist, maxdist, halflifedist, it.railgundistance);
310 ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, it.railgundistance);
312 if(accuracy_isgooddamage(this, it))
313 totaldmg += bdamage * f;
317 Damage (it, this, this, bdamage * f, deathtype, hitloc, it.railgunforce * ffs);
319 // create a small explosion to throw gibs around (if applicable)
320 //setorigin(explosion, hitloc);
321 //RadiusDamage (explosion, this, 10, 0, 50, NULL, NULL, 300, deathtype);
323 it.railgunhitloc = '0 0 0';
324 it.railgunhitsolidbackup = SOLID_NOT;
325 it.railgunhit = false;
326 it.railgundistance = 0;
329 // calculate hits and fired shots for hitscan
330 accuracy_add(this, this.(weaponentity).m_weapon.m_id, 0, min(bdamage, totaldmg));
332 trace_endpos = endpoint;
334 trace_dphitq3surfaceflags = endq3surfaceflags;
337 void fireBullet_trace_callback(vector start, vector hit, vector end)
339 if(vdist(hit - start, >, 16))
340 trailparticles(NULL, fireBullet_trace_callback_eff, start, hit);
341 WarpZone_trace_forent = NULL;
342 fireBullet_last_hit = NULL;
345 void fireBullet(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float force, float dtype, int tracereffects)
349 dir = normalize(dir + randomvec() * spread);
350 end = start + dir * max_shot_distance;
352 fireBullet_last_hit = NULL;
353 float solid_penetration_left = 1;
354 float total_damage = 0;
356 if(tracereffects & EF_RED)
357 fireBullet_trace_callback_eff = EFFECT_RIFLE;
358 else if(tracereffects & EF_BLUE)
359 fireBullet_trace_callback_eff = EFFECT_RIFLE_WEAK;
361 fireBullet_trace_callback_eff = EFFECT_BULLET;
363 float lag = ANTILAG_LATENCY(this);
366 if (!IS_REAL_CLIENT(this))
368 if(autocvar_g_antilag == 0 || this.cvar_cl_noantilag)
369 lag = 0; // only do hitscan, but no antilag
372 FOREACH_CLIENT(IS_PLAYER(it) && it != this, antilag_takeback(it, CS(it), time - lag));
373 IL_EACH(g_monsters, it != this,
375 antilag_takeback(it, it, time - lag);
379 // change shooter to SOLID_BBOX so the shot can hit corpses
380 int oldsolid = this.dphitcontentsmask;
382 this.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
384 WarpZone_trace_forent = this;
388 // TODO also show effect while tracing
389 WarpZone_TraceBox_ThroughZone(start, '0 0 0', '0 0 0', end, false, WarpZone_trace_forent, NULL, fireBullet_trace_callback);
390 dir = WarpZone_TransformVelocity(WarpZone_trace_transform, dir);
391 end = WarpZone_TransformOrigin(WarpZone_trace_transform, end);
392 start = trace_endpos;
393 entity hit = trace_ent;
395 // traced up to max_shot_distance and didn't hit anything at all
396 if (trace_fraction == 1.0)
399 // When hitting sky, stop.
400 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
403 // can't use noimpact, as we need to pass through walls
404 //if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
407 // if we hit "weapclip", bail out
409 // rationale of this check:
411 // any shader that is solid, nodraw AND trans is meant to clip weapon
412 // shots and players, but has no other effect!
414 // if it is not trans, it is caulk and should not have this side effect
417 // common/weapclip (intended)
418 // common/noimpact (is supposed to eat projectiles, but is erased anyway)
419 bool is_weapclip = false;
420 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
421 if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID))
422 if (!(trace_dphitcontents & DPCONTENTS_OPAQUE))
425 if(!hit || hit.solid == SOLID_BSP || hit.solid == SOLID_SLIDEBOX)
426 Damage_DamageInfo(start, damage * solid_penetration_left, 0, 0, max(1, force) * dir * solid_penetration_left, dtype, hit.species, this);
428 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).
430 fireBullet_last_hit = hit;
432 MUTATOR_CALLHOOK(FireBullet_Hit, this, hit, start, end, damage, this.(weaponentity));
433 damage = M_ARGV(4, float);
434 float g = accuracy_isgooddamage(this, hit);
435 Damage(hit, this, this, damage * solid_penetration_left, dtype, start, force * dir * solid_penetration_left);
436 // calculate hits for ballistic weapons
439 // do not exceed 100%
440 float added_damage = min(damage - total_damage, damage * solid_penetration_left);
441 total_damage += damage * solid_penetration_left;
442 accuracy_add(this, this.(weaponentity).m_weapon.m_id, 0, added_damage);
446 if (is_weapclip && !autocvar_g_ballistics_penetrate_clips)
450 // outside the world? forget it
451 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)
455 if(max_solid_penetration < 0)
457 else if(hit.ballistics_density < -1)
458 break; // -2: no solid penetration, ever
459 else if(hit.ballistics_density < 0)
460 maxdist = vlen(hit.maxs - hit.mins) + 1; // -1: infinite travel distance
461 else if(hit.ballistics_density == 0)
462 maxdist = max_solid_penetration * solid_penetration_left;
464 maxdist = max_solid_penetration * solid_penetration_left * hit.ballistics_density;
466 if(maxdist <= autocvar_g_ballistics_mindistance)
469 // move the entity along its velocity until it's out of solid, then let it resume
470 // The previously hit entity is ignored here!
471 traceline_inverted (start, start + dir * maxdist, MOVE_NORMAL, WarpZone_trace_forent, true, hit);
472 if(trace_fraction == 1) // 1: we never got out of solid
475 float dist_taken = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - start));
476 // fraction_used_of_what_is_left = dist_taken / maxdist
477 // solid_penetration_left = solid_penetration_left - solid_penetration_left * fraction_used_of_what_is_left
478 solid_penetration_left *= 1 - dist_taken / maxdist;
479 solid_penetration_left = max(solid_penetration_left, 0);
481 // Only show effect when going through a player (invisible otherwise)
482 if (hit && (hit.solid != SOLID_BSP))
483 if(vdist(trace_endpos - start, >, 4))
484 trailparticles(this, fireBullet_trace_callback_eff, start, trace_endpos);
486 start = trace_endpos;
488 if(hit.solid == SOLID_BSP)
489 Damage_DamageInfo(start, 0, 0, 0, max(1, force) * normalize(dir) * -solid_penetration_left, dtype, 0, this);
494 FOREACH_CLIENT(IS_PLAYER(it) && it != this, antilag_restore(it, CS(it)));
495 IL_EACH(g_monsters, it != this,
497 antilag_restore(it, it);
501 // restore shooter solid type
503 this.dphitcontentsmask = oldsolid;