]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Add various patches from Juhu/strafehud-fixes branch
authorJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Sun, 11 Sep 2022 00:20:23 +0000 (02:20 +0200)
committerJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Sun, 11 Sep 2022 00:20:23 +0000 (02:20 +0200)
_hud_common.cfg
qcsrc/client/hud/panel/strafehud.qc
qcsrc/client/hud/panel/strafehud.qh

index 2817cffa29b9c5ced0294e6eb540ceece00b7b9b..c1f6b96a951bcabab4be8a0b2a61feaca41cc7d0 100644 (file)
@@ -200,7 +200,7 @@ seta hud_panel_strafehud_timeout_air "0.1" "time (in seconds) after take off bef
 seta hud_panel_strafehud_timeout_ground "0.03333333" "time (in seconds) after landing before changing to non-air strafe physics (visually more consistent hud while strafe turning when touching the floor after every hop)"
 seta hud_panel_strafehud_timeout_turn "0.1" "time (in seconds) after releasing the strafe keys before changing mode (visually more consistent hud while switching between left/right strafe turning)"
 seta hud_panel_strafehud_antiflicker_angle "0.01" "how many degrees from 0° to 180° the hud ignores if it could cause visual disturbances otherwise (and to counter rounding errors)"
-seta hud_panel_strafehud_antiflicker_speed "0.0001" "how many qu/s the hud ignores if it could cause visual disturbances otherwise (and to counter rounding errors)"
+seta hud_panel_strafehud_fps_update "0.5" "update interval (in seconds) of the frametime to calculate the optimal angle, smaller values may cause flickering"
 
 // hud panel aliases
 alias quickmenu "cl_cmd hud quickmenu ${* ?}"
index d656d8a005af4f8fd287738156856dbe6bd4bdb2..2962cbe6e182b797378cf9e2333d2bb0bde01cc1 100644 (file)
@@ -31,20 +31,8 @@ void HUD_StrafeHUD_Export(int fh)
 
 float hidden_width;
 int direction;
-float demo_angle = -37;
-float demo_direction = 1;
-float demo_time = 0;
-bool state_onground = false;
-float state_onground_time = 0;
-bool state_strafekeys = false;
-float state_strafekeys_time = 0;
-bool turn = false;
-float turnangle;
-float turnspeed;
-float turnaccel;
-bool fwd = true;
-float starttime = 0;
-float startspeed = -1;
+
+float GeomLerp(float a, float _lerp, float b); // declare GeomLerp here since there's no header file for it
 
 void HUD_StrafeHUD()
 {
@@ -105,19 +93,57 @@ void HUD_StrafeHUD()
     // draw strafehud
     if(csqcplayer && strafeplayer)
     {
+        float strafe_waterlevel;
+
+        // get the player waterlevel without affecting the player entity
+        {
+            // store old values
+            void old_contentstransition(int, int) = strafeplayer.contentstransition;
+            float old_watertype = strafeplayer.watertype;
+            float old_waterlevel = strafeplayer.waterlevel;
+
+            strafeplayer.contentstransition = func_null; // unset the contentstransition function if present
+            _Movetype_CheckWater(strafeplayer);
+            strafe_waterlevel = strafeplayer.waterlevel; // store the player waterlevel
+
+            // restore old values
+            strafeplayer.contentstransition = old_contentstransition;
+            strafeplayer.watertype = old_watertype;
+            strafeplayer.waterlevel = old_waterlevel;
+        }
+
+        // presistent
+        static float demo_angle = -37;
+        static float demo_direction = 1;
+        static float demo_time = 0;
+        static bool state_onground = false;
+        static float state_onground_time = 0;
+        static bool state_strafekeys = false;
+        static float state_strafekeys_time = 0;
+        static bool turn = false;
+        static float turnangle;
+        static float turnspeed;
+        static float turnaccel;
+        static bool fwd = true;
+        static float strafe_dt_time = 0;
+        static int strafe_dt_count = 0;
+        static float strafe_dt_sum = 0;
+        static float strafe_dt_avg = 0;
+
         // physics
         bool   onground                      = islocal ? IS_ONGROUND(strafeplayer) : !(strafeplayer.anim_implicit_state & ANIMIMPLICITSTATE_INAIR);
         bool   strafekeys;
-        bool   swimming                      = strafeplayer.waterlevel >= WATERLEVEL_SWIMMING;
+        bool   swimming                      = strafe_waterlevel >= WATERLEVEL_SWIMMING; // the hud will not work well while swimming
         bool   spectating                    = entcs_GetSpecState(strafeplayer.sv_entnum) == ENTCS_SPEC_PURE;
         float  speed                         = !autocvar__hud_configure ? vlen(vec2(csqcplayer.velocity)) : 1337; // use local csqcmodel entity for this even when spectating, flickers too much otherwise
-        float  crouch_mod                    = IS_DUCKED(csqcplayer) && !swimming ? .5 : 1;
-        float  water_mod                     = swimming ? .7 : 1; // very simplified water physics, the hud will not work well (and is not supposed to) while swimming
+        float  maxspeed_mod                  = IS_DUCKED(csqcplayer) ? .5 : 1;
         float  maxspeed_phys                 = onground ? PHYS_MAXSPEED(strafeplayer) : PHYS_MAXAIRSPEED(strafeplayer);
-        float  maxspeed                      = !autocvar__hud_configure ? maxspeed_phys * crouch_mod * water_mod : 320;
+        float  maxspeed                      = !autocvar__hud_configure ? maxspeed_phys * maxspeed_mod : 320;
         float  movespeed;
+        float  bestspeed;
         float  maxaccel_phys                 = onground ? PHYS_ACCELERATE(strafeplayer) : PHYS_AIRACCELERATE(strafeplayer);
-        float  maxaccel                      = !autocvar__hud_configure ? maxaccel_phys * crouch_mod * water_mod : 1;
+        float  maxaccel                      = !autocvar__hud_configure ? maxaccel_phys : 1;
+        float  frametime_phys;
         float  vel_angle                     = vectoangles(strafeplayer.velocity).y - (vectoangles(strafeplayer.velocity).y > 180 ? 360 : 0); // change the range from 0° - 360° to -180° - 180° to match how view_angle represents angles
         float  view_angle                    = PHYS_INPUT_ANGLES(strafeplayer).y;
         float  angle;
@@ -132,11 +158,10 @@ void HUD_StrafeHUD()
         float  length_conversion_factor      = GetLengthUnitFactor(autocvar_hud_panel_strafehud_unit);
         int    length_decimals               = autocvar_hud_panel_strafehud_unit >= 3 && autocvar_hud_panel_strafehud_unit <= 5 ? 6 : 2; // use more decimals when displaying km or miles
         float  antiflicker_angle             = bound(0, autocvar_hud_panel_strafehud_antiflicker_angle, 180);
-        float  antiflicker_speed             = max(0, autocvar_hud_panel_strafehud_antiflicker_speed);
         float  minspeed;
         float  shift_offset                  = 0;
         bool   straight_overturn             = false;
-        bool   immobile                      = speed <= (swimming ? antiflicker_speed : 0);
+        bool   immobile                      = speed <= 0;
         float  hudangle;
         float  neutral_offset;
         float  neutral_width;
@@ -170,6 +195,27 @@ void HUD_StrafeHUD()
         if(!autocvar_hud_panel_strafehud_uncapped)
             arrow_size = max(arrow_size, 1);
 
+        // determine frametime
+        if((csqcplayer_status == CSQCPLAYERSTATUS_PREDICTED) && (input_timelength > 0))
+            frametime_phys = input_timelength;
+        else
+            frametime_phys = ticrate;
+
+        if(frametime_phys > .05) // server splits frames longer than 50 ms into two moves
+            frametime_phys /= 2; // doesn't ensure frames are smaller than 50 ms, just splits large frames in half, matches server behaviour
+
+        // calculate average frametime
+        strafe_dt_sum += frametime_phys;
+        ++strafe_dt_count;
+
+        if(((time - strafe_dt_time) > autocvar_hud_panel_strafehud_fps_update) || (strafe_dt_time == 0))
+        {
+            strafe_dt_avg = strafe_dt_sum / strafe_dt_count;
+
+            strafe_dt_time = time;
+            strafe_dt_count = strafe_dt_sum = 0;
+        }
+
         // determine whether the player is pressing forwards or backwards keys
         if(islocal) // if entity is local player
         {
@@ -299,7 +345,7 @@ void HUD_StrafeHUD()
         }
         state_strafekeys = strafekeys;
 
-        if((!strafekeys && vlen(vec2(movement)) > 0) || swimming || autocvar__hud_configure)
+        if((!strafekeys && vlen(vec2(movement)) > 0) || autocvar__hud_configure)
         {
             turn = false;
         }
@@ -320,27 +366,18 @@ void HUD_StrafeHUD()
                     turnangle = wishangle;
 
                     // calculate the maximum air strafe speed and acceleration
-                    if(PHYS_MAXAIRSPEED(strafeplayer) == 0){
-                        maxspeed = 0;
-                    }
-                    else if(PHYS_MAXAIRSTRAFESPEED(strafeplayer) == 0 || PHYS_MAXAIRSPEED(strafeplayer) <= PHYS_MAXAIRSTRAFESPEED(strafeplayer)){
-                        maxspeed = PHYS_MAXAIRSPEED(strafeplayer);
-                    }
-                    else{
-                        maxspeed = PHYS_MAXAIRSPEED(strafeplayer) * pow(fabs(PHYS_MAXAIRSTRAFESPEED(strafeplayer) / PHYS_MAXAIRSPEED(strafeplayer)), 1 - (90 - fabs(wishangle)) / 45); // no modifiers here because they don't affect air strafing
+                    float strafity = 1 - (90 - fabs(wishangle)) / 45;
+
+                    if(PHYS_MAXAIRSTRAFESPEED(strafeplayer) != 0){
+                        maxspeed = GeomLerp(PHYS_MAXAIRSPEED(strafeplayer), strafity, PHYS_MAXAIRSTRAFESPEED(strafeplayer));
+                        maxspeed = min(maxspeed, PHYS_MAXAIRSPEED(strafeplayer) * maxspeed_mod);
                     }
                     turnspeed = vlen(vec2(movement));
                     if(turnspeed == 0) turnspeed = maxspeed;
                     else turnspeed = min(turnspeed, maxspeed);
 
-                    if(PHYS_AIRACCELERATE(strafeplayer) == 0){
-                        maxaccel = 0;
-                    }
-                    else if(PHYS_AIRSTRAFEACCELERATE(strafeplayer) == 0 || PHYS_AIRACCELERATE(strafeplayer) <= PHYS_AIRSTRAFEACCELERATE(strafeplayer)){
-                        maxaccel = PHYS_AIRACCELERATE(strafeplayer);
-                    }
-                    else{
-                        maxaccel = PHYS_AIRACCELERATE(strafeplayer) * pow(fabs(PHYS_AIRSTRAFEACCELERATE(strafeplayer) / PHYS_AIRACCELERATE(strafeplayer)), 1 - (90 - fabs(wishangle)) / 45); // no modifiers here because they don't affect air strafing
+                    if(PHYS_AIRSTRAFEACCELERATE(strafeplayer) != 0) {
+                        maxaccel = GeomLerp(PHYS_AIRACCELERATE(strafeplayer), strafity, PHYS_AIRSTRAFEACCELERATE(strafeplayer));
                     }
                     turnaccel = maxaccel;
                 }
@@ -365,19 +402,34 @@ void HUD_StrafeHUD()
             {
                 if((keys & KEY_JUMP) && ((time - state_onground_time) < autocvar_hud_panel_strafehud_timeout_ground)) // if ground timeout hasn't expired yet use air accelerate
                 {
-                    maxaccel = !autocvar__hud_configure ? PHYS_AIRACCELERATE(strafeplayer) * crouch_mod * water_mod : 1;
+                    maxaccel = !autocvar__hud_configure ? PHYS_AIRACCELERATE(strafeplayer) : 1;
                 }
             }
             else
             {
                 if(!(keys & KEY_JUMP) && ((time - state_onground_time) < autocvar_hud_panel_strafehud_timeout_air)) // if air timeout hasn't expired yet use ground accelerate
                 {
-                    maxaccel = !autocvar__hud_configure ? PHYS_ACCELERATE(strafeplayer) * crouch_mod * water_mod : 1;
+                    maxaccel = !autocvar__hud_configure ? PHYS_ACCELERATE(strafeplayer) : 1;
                 }
             }
         }
 
-        minspeed = autocvar_hud_panel_strafehud_switch_minspeed < 0 ? (movespeed - maxaccel) + antiflicker_speed : autocvar_hud_panel_strafehud_switch_minspeed;
+        maxaccel *= strafe_dt_avg * movespeed;
+        bestspeed = max(movespeed - maxaccel, 0);
+
+        float strafespeed = speed; // speed minus friction
+
+        if((strafespeed > 0) && onground){
+            float strafefriction = IS_ONSLICK(strafeplayer) ? PHYS_FRICTION_SLICK(strafeplayer) : PHYS_FRICTION(strafeplayer);
+            float f = 1 - strafe_dt_avg * strafefriction * max(PHYS_STOPSPEED(strafeplayer) / strafespeed, 1);
+
+            if(f <= 0)
+                strafespeed = 0;
+            else
+                strafespeed *= f;
+        }
+
+        minspeed = autocvar_hud_panel_strafehud_switch_minspeed < 0 ? bestspeed + (speed - strafespeed) : autocvar_hud_panel_strafehud_switch_minspeed;
 
         // get current strafing angle ranging from -180° to +180°
         if(!autocvar__hud_configure)
@@ -479,8 +531,8 @@ void HUD_StrafeHUD()
         }
 
         // best angle to strafe at
-        bestangle = (speed > fabs(movespeed - maxaccel) ? acos(fabs(movespeed - maxaccel) / speed) * RAD2DEG * (direction < 0 ? -1 : 1) : 0);
-        prebestangle = (speed > fabs(movespeed) ? acos(fabs(movespeed) / speed) * RAD2DEG * (direction < 0 ? -1 : 1) : 0);
+        bestangle = (strafespeed > bestspeed ? acos(bestspeed / strafespeed) * RAD2DEG * (direction < 0 ? -1 : 1) : 0);
+        prebestangle = (strafespeed > movespeed ? acos(movespeed / strafespeed) * RAD2DEG * (direction < 0 ? -1 : 1) : 0);
         odd_bestangle = -bestangle - wishangle;
         bestangle -= wishangle;
         prebestangle -= wishangle;
@@ -600,7 +652,7 @@ void HUD_StrafeHUD()
             HUD_Panel_DrawStrafeHUD(accelzone_right_offset, accelzone_width, autocvar_hud_panel_strafehud_bar_accel_color, autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 2);
             HUD_Panel_DrawStrafeHUD(preaccelzone_right_offset, preaccelzone_width, autocvar_hud_panel_strafehud_bar_preaccel_color, autocvar_hud_panel_strafehud_bar_preaccel_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 0);
 
-            // draw overturn zone
+            // draw overturn zone (technically incorrect, acceleration decreases after 90 degrees but speed loss happens a little bit after 90 degrees, however due to sv_airstopaccelerate that's hard to calculate)
             HUD_Panel_DrawStrafeHUD(overturn_offset, overturn_width, autocvar_hud_panel_strafehud_bar_overturn_color, autocvar_hud_panel_strafehud_bar_overturn_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 3);
 
             // draw neutral zone
@@ -645,12 +697,13 @@ void HUD_StrafeHUD()
 
             if(!autocvar_hud_panel_strafehud_uncapped)
                 slicksteps = min(slicksteps, 4);
-            slicksteps = 90 / pow(2, slicksteps);
+            slicksteps = 90 / 2 ** slicksteps;
 
-            if(islocal) slickdetected = IS_ONSLICK(strafeplayer); // don't need to traceline if already touching slick
+            if(islocal) slickdetected = (IS_ONSLICK(strafeplayer) || (IS_ONGROUND(strafeplayer) && (PHYS_FRICTION(strafeplayer) == 0))); // don't need to traceline if already touching slick
 
             // traceline into every direction
             trace_dphitq3surfaceflags = 0;
+            vector traceorigin = strafeplayer.origin + '0 0 1' * strafeplayer.mins.z;
             for(float i = 0; i < 360 && !slickdetected; i += slicksteps)
             {
                 vector slickoffset;
@@ -664,14 +717,14 @@ void HUD_StrafeHUD()
                         slickoffset.x = sin(j * DEG2RAD) * slickrotate;
                         slickoffset.y = cos(j * DEG2RAD) * slickrotate;
 
-                        traceline(strafeplayer.origin, strafeplayer.origin + slickoffset, MOVE_WORLDONLY, NULL);
+                        traceline(traceorigin, traceorigin + slickoffset, MOVE_NOMONSTERS, strafeplayer);
                         if((PHYS_FRICTION(strafeplayer) == 0 && trace_fraction < 1) || trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) slickdetected = true;
                     }
                 }
                 else
                 {
                     slickoffset.x = slickoffset.y = 0;
-                    traceline(strafeplayer.origin, strafeplayer.origin + slickoffset, MOVE_WORLDONLY, NULL);
+                    traceline(traceorigin, traceorigin + slickoffset, MOVE_NOMONSTERS, strafeplayer);
                     if((PHYS_FRICTION(strafeplayer) == 0 && trace_fraction < 1) || trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) slickdetected = true;
                 }
             }
@@ -706,7 +759,7 @@ void HUD_StrafeHUD()
             drawfill(panel_pos + eX * (indicator_direction ? 0 : panel_size.x - direction_size_horizontal.x) + eY * panel_size.y, direction_size_horizontal, autocvar_hud_panel_strafehud_direction_color, autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
         }
 
-        if(speed <= (fabs(movespeed - maxaccel) + antiflicker_speed) && !immobile)
+        if(strafespeed <= bestspeed && !immobile)
         {
             bestangle_anywhere = true; // moving forward should suffice to gain speed
         }
@@ -822,7 +875,8 @@ void HUD_StrafeHUD()
         // show speed when crossing the start trigger
         if(autocvar_hud_panel_strafehud_startspeed_fade > 0)
         {
-            float text_alpha = 0;
+            static float startspeed = 0, starttime = 0; // displayed value and timestamp for fade out
+
             if((race_nextcheckpoint == 1) || (race_checkpoint == 254 && race_nextcheckpoint == 255)) // check if the start trigger was hit (will also trigger if the finish trigger was hit if those have the same ID)
             {
                 if(starttime != race_checkpointtime)
@@ -831,16 +885,10 @@ void HUD_StrafeHUD()
                     startspeed = speed;
                 }
             }
-            if(startspeed >= 0)
-            {
-                text_alpha = cos(((time - starttime) / autocvar_hud_panel_strafehud_startspeed_fade) * 90 * DEG2RAD); // fade non-linear like the physics panel does
-                if((time - starttime) > autocvar_hud_panel_strafehud_startspeed_fade)
-                {
-                    startspeed = -1;
-                }
-            }
-            if(startspeed >= 0 && text_alpha > 0 && autocvar_hud_panel_strafehud_startspeed_size > 0)
+
+            if((starttime > 0) && ((time - starttime) <= autocvar_hud_panel_strafehud_startspeed_fade) && autocvar_hud_panel_strafehud_startspeed_size > 0)
             {
+                float text_alpha = cos(((time - starttime) / autocvar_hud_panel_strafehud_startspeed_fade) * 90 * DEG2RAD); // fade non-linear like the physics panel does
                 vector startspeed_size = panel_size;
                 startspeed_size.y = autocvar_hud_panel_strafehud_startspeed_size;
                 if(!autocvar_hud_panel_strafehud_uncapped)
@@ -863,33 +911,33 @@ void HUD_StrafeHUD()
                 drawstring_aspect(panel_pos + eY * (panel_size.y + text_offset), strcat(ftos_decimals(startspeed * speed_conversion_factor, 2), autocvar_hud_panel_strafehud_unit_show ? speed_unit : ""), startspeed_size, autocvar_hud_panel_strafehud_startspeed_color, text_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
             }
         }
-        else
-        {
-            starttime = 0;
-            startspeed = -1;
-        }
 
         // show height achieved by a single jump
+        // FIXME: checking z position differences is unreliable (warpzones, teleporter, kill, etc) but using velocity to calculate jump height would be
+        //        inaccurate in hud code (possibly different tick rate, doesn't run when hud isn't drawn, rounding errors)
         if(autocvar_hud_panel_strafehud_jumpheight_fade > 0 && autocvar_hud_panel_strafehud_jumpheight_size > 0)
         {
             static float height_min = 0, height_max = 0; // ground and peak of jump z coordinates
             static float jumpheight = 0, jumptime = 0;   // displayed value and timestamp for fade out
 
-            // tries to catch kill and spectate but those are not reliable, should just hook to kill/spectate/teleport and reset jump height there
-            if((strafeplayer.velocity.z <= 0 && height_max >= strafeplayer.origin.z) || onground || swimming || IS_DEAD(strafeplayer) || spectating)
+            // tries to catch kill and spectate but those are not reliable
+            if((strafeplayer.velocity.z <= 0) || onground || swimming || IS_DEAD(strafeplayer) || spectating)
             {
                 height_min = height_max = strafeplayer.origin.z;
             }
             else if(strafeplayer.origin.z > height_max)
             {
                 height_max = strafeplayer.origin.z;
-                jumpheight = (height_max - height_min) * length_conversion_factor;
+                float jumpheight_new = (height_max - height_min) * length_conversion_factor;
 
-                if(jumpheight > max(autocvar_hud_panel_strafehud_jumpheight_min, 0))
+                if(jumpheight_new > max(autocvar_hud_panel_strafehud_jumpheight_min, 0))
+                {
+                    jumpheight = jumpheight_new;
                     jumptime = time;
+                }
             }
 
-            if((time - jumptime) <= autocvar_hud_panel_strafehud_jumpheight_fade)
+            if((jumptime > 0) && (time - jumptime) <= autocvar_hud_panel_strafehud_jumpheight_fade)
             {
                 float text_alpha = cos(((time - jumptime) / autocvar_hud_panel_strafehud_jumpheight_fade) * 90 * DEG2RAD); // fade non-linear like the physics panel does
                 vector jumpheight_size = panel_size;
index 63ebbed98d16a941b162bf2cc0b289fa995c3527..e5ce48a7cad48488d1f3c9e1767e9b091b2b26f4 100644 (file)
@@ -2,8 +2,8 @@
 #include "../panel.qh"
 
 AUTOCVAR_SAVE(hud_panel_strafehud, int, 3, "enable this panel, 1 = show if not observing, 2 = show always, 3 = show only in race/cts if not observing");
-AUTOCVAR_SAVE(hud_panel_strafehud_dynamichud, bool, true, "apply the dynamic hud effects to this panel");
 AUTOCVAR_SAVE(_hud_panel_strafehud_demo, bool, false, "strafehud changes angle during configure");
+AUTOCVAR_SAVE(hud_panel_strafehud_dynamichud, bool, true, "apply the dynamic hud effects to this panel");
 AUTOCVAR_SAVE(hud_panel_strafehud_mode, int, 0, "strafehud mode which controls whether the strafehud is centered at \"0\" = view angle, \"1\" = velocity angle");
 AUTOCVAR_SAVE(hud_panel_strafehud_range, float, 0, "the angle range up to 360 degrees displayed on the strafehud, \"0\" = dynamic (chooses the minimum range required to still see the whole area needed for accelerating)");
 AUTOCVAR_SAVE(hud_panel_strafehud_style, int, 2, "\"0\" = no styling, \"1\" = progress bar style for the strafe bar, \"2\" = gradient for the strafe bar");
@@ -55,7 +55,7 @@ AUTOCVAR_SAVE(hud_panel_strafehud_timeout_air, float, 0.1, "time (in seconds) af
 AUTOCVAR_SAVE(hud_panel_strafehud_timeout_ground, float, 0.03333333, "time (in seconds) after landing before changing to non-air strafe physics (visually more consistent hud while strafe turning when touching the floor after every hop)");
 AUTOCVAR_SAVE(hud_panel_strafehud_timeout_turn, float, 0.1, "time (in seconds) after releasing the strafe keys before changing mode (visually more consistent hud while switching between left/right strafe turning)");
 AUTOCVAR_SAVE(hud_panel_strafehud_antiflicker_angle, float, 0.01, "how many degrees from 0° to 180° the hud ignores if it could cause visual disturbances otherwise (and to counter rounding errors)");
-AUTOCVAR_SAVE(hud_panel_strafehud_antiflicker_speed, float, 0.0001, "how many qu/s the hud ignores if it could cause visual disturbances otherwise (and to counter rounding errors)");
+AUTOCVAR_SAVE(hud_panel_strafehud_fps_update, float, 0.5, "update interval (in seconds) of the frametime to calculate the optimal angle, smaller values may cause flickering");
 
 void HUD_Panel_DrawStrafeHUD(float, float, vector, float, int, int);
 vector StrafeHUD_mixColors(vector, vector, float);