From 85af045ce99f1c5e4322d92a82d23df9f9b66f8c Mon Sep 17 00:00:00 2001 From: Mario Date: Tue, 8 Jun 2021 09:42:30 +1000 Subject: [PATCH] Add a very basic means of displaying a client-side beam when firing vaporizer shots to aid those with a high ping --- qcsrc/client/hud/crosshair.qc | 26 +++---- qcsrc/client/hud/crosshair.qh | 2 + qcsrc/client/main.qc | 1 + qcsrc/client/main.qh | 2 - qcsrc/client/weapons/_mod.inc | 1 + qcsrc/client/weapons/_mod.qh | 1 + qcsrc/client/weapons/tracing.qc | 97 ++++++++++++++++++++++++ qcsrc/client/weapons/tracing.qh | 16 ++++ qcsrc/common/weapons/weapon.qh | 2 + qcsrc/common/weapons/weapon/vaporizer.qc | 55 +++++++++++--- qcsrc/common/wepent.qh | 4 + xonotic-client.cfg | 3 + 12 files changed, 185 insertions(+), 25 deletions(-) create mode 100644 qcsrc/client/weapons/tracing.qc create mode 100644 qcsrc/client/weapons/tracing.qh diff --git a/qcsrc/client/hud/crosshair.qc b/qcsrc/client/hud/crosshair.qc index c697268eb..dddfbb040 100644 --- a/qcsrc/client/hud/crosshair.qc +++ b/qcsrc/client/hud/crosshair.qc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -49,23 +50,18 @@ void TrueAim_Init() (trueaim_rifle = new_pure(trueaim_rifle)).dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_CORPSE; } -float EnemyHitCheck() +int EnemyHitCheck(bool trace_crosshair) { - float t, n; - wcross_origin = project_3d_to_2d(trace_endpos); - wcross_origin.z = 0; - if(trace_ent) - n = trace_ent.entnum; - else - n = trace_networkentity; + if(trace_crosshair) + wcross_origin = vec2(project_3d_to_2d(trace_endpos)); + int n = (trace_ent) ? trace_ent.entnum : trace_networkentity; if(n < 1) return SHOTTYPE_HITWORLD; if(n > maxclients) return SHOTTYPE_HITWORLD; - t = entcs_GetTeam(n - 1); - if(teamplay) - if(t == myteam) - return SHOTTYPE_HITTEAM; + int t = entcs_GetTeam(n - 1); + if(teamplay && t == myteam) + return SHOTTYPE_HITTEAM; if(t == NUM_SPECTATOR) return SHOTTYPE_HITWORLD; return SHOTTYPE_HITENEMY; @@ -100,7 +96,7 @@ float TrueAimCheck(entity wepent) if(zoomscript_caught) { tracebox(view_origin, '0 0 0', '0 0 0', view_origin + view_forward * max_shot_distance, mv, ta); - return EnemyHitCheck(); + return EnemyHitCheck(true); } break; case WEP_DEVASTATOR: // projectile has a size! @@ -147,7 +143,7 @@ float TrueAimCheck(entity wepent) w_shotorg = trace_endpos - view_forward * nudge; tracebox(w_shotorg, mi, ma, trueaimpoint, MOVE_NORMAL, ta); - shottype = EnemyHitCheck(); + shottype = EnemyHitCheck(true); if(shottype != SHOTTYPE_HITWORLD) return shottype; @@ -236,6 +232,8 @@ void HUD_Crosshair(entity this) return; } + HUD_Crosshair_ClientBeam(this); + float f, i, j; vector v; if(!scoreboard_active && !camera_active && intermission != 2 && !STAT(GAME_STOPPED) && !autocvar_cl_lockview diff --git a/qcsrc/client/hud/crosshair.qh b/qcsrc/client/hud/crosshair.qh index 4cd7715af..f1e7221a8 100644 --- a/qcsrc/client/hud/crosshair.qh +++ b/qcsrc/client/hud/crosshair.qh @@ -63,3 +63,5 @@ vector crosshair_getcolor(entity this, float health_stat); void TrueAim_Init(); void HUD_Crosshair(entity this); void DrawReticle(entity this); + +int EnemyHitCheck(bool trace_crosshair); diff --git a/qcsrc/client/main.qc b/qcsrc/client/main.qc index 757effd99..4ab08609b 100644 --- a/qcsrc/client/main.qc +++ b/qcsrc/client/main.qc @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/qcsrc/client/main.qh b/qcsrc/client/main.qh index 83417c08d..6206f4916 100644 --- a/qcsrc/client/main.qh +++ b/qcsrc/client/main.qh @@ -137,8 +137,6 @@ const int HOOK_END = 2; .float ping, ping_packetloss, ping_movementloss; -float g_trueaim_minrange; - int hud; float view_quality; diff --git a/qcsrc/client/weapons/_mod.inc b/qcsrc/client/weapons/_mod.inc index 5fb71ce1e..bd1e1e842 100644 --- a/qcsrc/client/weapons/_mod.inc +++ b/qcsrc/client/weapons/_mod.inc @@ -1,2 +1,3 @@ // generated file; do not modify #include +#include diff --git a/qcsrc/client/weapons/_mod.qh b/qcsrc/client/weapons/_mod.qh index f72a914c6..09ff6e359 100644 --- a/qcsrc/client/weapons/_mod.qh +++ b/qcsrc/client/weapons/_mod.qh @@ -1,2 +1,3 @@ // generated file; do not modify #include +#include diff --git a/qcsrc/client/weapons/tracing.qc b/qcsrc/client/weapons/tracing.qc new file mode 100644 index 000000000..30a4bb19a --- /dev/null +++ b/qcsrc/client/weapons/tracing.qc @@ -0,0 +1,97 @@ +#include "tracing.qh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int W_SetupShot_Dir_ProjectileSize_Range(entity ent, entity wepent, vector s_forward, vector mi, vector ma, float antilag, float recoil, Sound snd, float chan, float maxdamage, float range) +{ + 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? + vector vecs; + int oldsolid = ent.dphitcontentsmask; + if (IS_PLAYER(ent) && wepent.activeweapon == WEP_RIFLE) + ent.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_CORPSE; + else + ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE; + WarpZone_TraceLine(ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NOMONSTERS, ent); + ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE; + + vector forward, right, up; + forward = v_forward; + right = v_right; + up = v_up; + w_shotend = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); // warpzone support + v_forward = forward; + v_right = right; + v_up = up; + + // perform the test now as future traces will affect the accuracy of the test + return = EnemyHitCheck(false); + + // un-adjust trueaim if shotend is too close + if(vdist(w_shotend - (ent.origin + ent.view_ofs), <, g_trueaim_minrange)) + w_shotend = ent.origin + ent.view_ofs + s_forward * g_trueaim_minrange; + + vector md = wepent.movedir; + if(md.x > 0) + vecs = md; + else + vecs = '0 0 0'; + + vector dv = right * -vecs.y + up * vecs.z; + w_shotorg = ent.origin + ent.view_ofs + dv; + + int oldsurfaceflags = trace_dphitq3surfaceflags; + + // now move the shotorg forward as much as requested if possible + tracebox(w_shotorg, mi, ma, w_shotorg + forward * (vecs.x + nudge), MOVE_NORMAL, ent); + w_shotorg = trace_endpos - forward * nudge; + // calculate the shotdir from the chosen shotorg + w_shotdir = normalize(w_shotend - w_shotorg); + + // restore flags for later use + trace_dphitq3surfaceflags = oldsurfaceflags; + + //vector prevdir = w_shotdir; + //vector prevorg = w_shotorg; + //vector prevend = w_shotend; + + ent.dphitcontentsmask = oldsolid; + + // nudge w_shotend so a trace to w_shotend hits + w_shotend = w_shotend + normalize(w_shotend - w_shotorg) * nudge; + //if(w_shotend != prevend) { printf("CLIENT: shotEND differs: %s - %s\n", vtos(w_shotend), vtos(prevend)); } + //if(w_shotorg != prevorg) { printf("CLIENT: shotORG differs: %s - %s\n", vtos(w_shotorg), vtos(prevorg)); } + //if(w_shotdir != prevdir) { printf("CLIENT: shotDIR differs: %s - %s\n", vtos(w_shotdir), vtos(prevdir)); } +} + +void HUD_Crosshair_ClientBeam(entity this) +{ + // TODO: make player_blocked a generic function for use here! + if(time < STAT(GAMESTARTTIME) || time < STAT(ROUNDSTARTTIME) || STAT(FROZEN) || spectatee_status) + return; + + entity localplayer = playerslots[player_localnum]; + if(!(autocvar_cl_clientbeams > 0 || (localplayer.ping >= autocvar_cl_clientbeams_minping && autocvar_cl_clientbeams != -1))) + return; + + bool button_atck = (input_buttons & BIT(0)); + bool button_atck2 = (input_buttons & BIT(2)); + + vector viewangles = getpropertyvec(VF_CL_VIEWANGLES); + makevectors(viewangles); + + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + entity wepent = viewmodels[slot]; + Weapon wep = wepent.activeweapon; + if(wep != WEP_Null) + wep.wr_clientbeam(wep, localplayer, wepent, button_atck | (button_atck2 << 1)); + } +} diff --git a/qcsrc/client/weapons/tracing.qh b/qcsrc/client/weapons/tracing.qh new file mode 100644 index 000000000..c72c20a03 --- /dev/null +++ b/qcsrc/client/weapons/tracing.qh @@ -0,0 +1,16 @@ +#pragma once + +#include + +AUTOCVAR_SAVE(cl_clientbeams, int, 0, "Draw a client side beam when firing some weapons. -1: always disabled, 0: disabled unless ping is high, 1: enabled"); +AUTOCVAR_SAVE(cl_clientbeams_minping, int, 160, "Minimum ping for the client-side beams to automatically enable"); + +float g_trueaim_minrange; +vector w_shotorg; +vector w_shotdir; +vector w_shotend; + +// TODO: make this a common function! +int W_SetupShot_Dir_ProjectileSize_Range(entity ent, entity wepent, vector s_forward, vector mi, vector ma, float antilag, float recoil, Sound snd, float chan, float maxdamage, float range); + +void HUD_Crosshair_ClientBeam(entity this); diff --git a/qcsrc/common/weapons/weapon.qh b/qcsrc/common/weapons/weapon.qh index 67f646cbe..81632a709 100644 --- a/qcsrc/common/weapons/weapon.qh +++ b/qcsrc/common/weapons/weapon.qh @@ -101,6 +101,8 @@ CLASS(Weapon, Object) METHOD(Weapon, wr_resetplayer, void(Weapon this, entity actor)) {} /** (CLIENT) impact effect for weapon explosion */ METHOD(Weapon, wr_impacteffect, void(Weapon this, entity actor)) {} + /** (CLIENT) client-side logic run every frame, useful for drawing predicted beams */ + METHOD(Weapon, wr_clientbeam, void(Weapon this, entity actor, entity wepent, int fire)) {} /** (SERVER) called whenever a player dies */ METHOD(Weapon, wr_playerdeath, void(Weapon this, entity actor, .entity weaponentity)) {} /** (SERVER) logic to run when weapon is lost */ diff --git a/qcsrc/common/weapons/weapon/vaporizer.qc b/qcsrc/common/weapons/weapon/vaporizer.qc index 6f4d4aebf..4566020bc 100644 --- a/qcsrc/common/weapons/weapon/vaporizer.qc +++ b/qcsrc/common/weapons/weapon/vaporizer.qc @@ -68,22 +68,20 @@ void VaporizerBeam_Draw(entity this) WarpZone_TrailParticles_WithMultiplier(NULL, particleeffectnum(EFFECT_VORTEX_BEAM), this.vorg1, this.vorg2, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE);*/ } -NET_HANDLE(TE_CSQC_VAPORBEAMPARTICLE, bool isNew) +void VaporizerBeam_Fire(entity this, bool isNew, vector org1, vector org2, bool enemy_hit, int owner_id, int shot_team) { - Net_Accept(vortex_beam); setthink(this, SUB_Remove); this.nextthink = time + bound(0, autocvar_cl_vaporizerbeam_lifetime, 10); this.draw = VaporizerBeam_Draw; if (isNew) IL_PUSH(g_drawables, this); this.drawmask = MASK_NORMAL; - this.vorg1 = ReadVector(); - this.vorg2 = ReadVector(); - this.cnt = ReadByte(); - int myowner = ReadByte(); - this.owner = playerslots[myowner - 1]; - this.sv_entnum = myowner; - this.team = ReadByte() - 1; + this.vorg1 = org1; + this.vorg2 = org2; + this.cnt = enemy_hit; + this.owner = (owner_id) ? playerslots[owner_id - 1] : NULL; + this.sv_entnum = owner_id; + this.team = shot_team; //pointparticles(EFFECT_VORTEX_MUZZLEFLASH, this.vorg1, normalize(this.vorg2 - this.vorg1) * 1000, 1); @@ -94,6 +92,19 @@ NET_HANDLE(TE_CSQC_VAPORBEAMPARTICLE, bool isNew) this.drawmask = MASK_NORMAL; delete(this); } +} + +NET_HANDLE(TE_CSQC_VAPORBEAMPARTICLE, bool isNew) +{ + Net_Accept(vortex_beam); + + vector org1 = ReadVector(); + vector org2 = ReadVector(); + bool enemy_hit = ReadByte(); + int myowner = ReadByte(); + int owner_team = ReadByte() - 1; + + VaporizerBeam_Fire(this, isNew, org1, org2, enemy_hit, myowner, owner_team); return true; } @@ -425,5 +436,31 @@ METHOD(Vaporizer, wr_zoom, bool(entity thiswep, entity actor)) return false; } } +.float last_beam; +METHOD(Vaporizer, wr_clientbeam, void(entity thiswep, entity actor, entity wepent, int fire)) +{ + if((fire & 1) && time > actor.last_beam) + { + // for traces we need the CSQC model! + entity realplayer = CSQCModel_server2csqc(actor.sv_entnum); + if(!realplayer) + realplayer = csqcplayer; // fall back to the global + int shottype = W_SetupShot_Dir_ProjectileSize_Range(realplayer, wepent, v_forward, '0 0 0', '0 0 0', false, 0, SND_Null, CH_WEAPON_A, 0, max_shot_distance); + bool hit = boolean(shottype == SHOTTYPE_HITENEMY || shottype == SHOTTYPE_HITTEAM); // also count team shots + + entity e = new(VaporizerBeam); + VaporizerBeam_Fire(e, true, w_shotorg, w_shotend, hit, actor.sv_entnum, myteam); + + if(!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) && !hit) + { + vector force = w_shotdir * WEP_CVAR_PRI(vaporizer, force); + traceline(w_shotend - normalize(force) * 16, w_shotend + normalize(force) * 16, MOVE_NOMONSTERS, NULL); + w_backoff = -1 * normalize(force); + vector org2 = w_shotend + w_backoff * 6; + pointparticles(EFFECT_VORTEX_IMPACT, org2, '0 0 0', 1); + } + actor.last_beam = time + WEP_CVAR_PRI(vaporizer, refire); + } +} #endif diff --git a/qcsrc/common/wepent.qh b/qcsrc/common/wepent.qh index 6d7128c33..c2872e2be 100644 --- a/qcsrc/common/wepent.qh +++ b/qcsrc/common/wepent.qh @@ -1,5 +1,7 @@ #pragma once +#include + REGISTER_NET_LINKED(ENT_CLIENT_WEPENT) REGISTER_NET_TEMP(CLIENT_WEPENT) @@ -39,6 +41,8 @@ REGISTER_NET_TEMP(CLIENT_WEPENT) .int m_skin; + .vector movedir; + // only for Porto .bool angles_held_status; .vector angles_held; diff --git a/xonotic-client.cfg b/xonotic-client.cfg index c62cb5540..93988fcb5 100644 --- a/xonotic-client.cfg +++ b/xonotic-client.cfg @@ -901,6 +901,9 @@ set cl_useenginerefdef 0 set cl_rollkillspeed 10 +seta cl_clientbeams 0 "Draw a client side beam when firing some weapons. -1: always disabled, 0: disabled unless ping is high, 1: enabled" +seta cl_clientbeams_minping 160 "Minimum ping for the client-side beams to automatically enable" + // Facility for config.cfg use ONLY. // Interpreted in post-config.cfg. seta menu_forced_saved_cvars "" "These cvars will always be saved, despite engine/Xonotic cvar saving status" -- 2.39.2