]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'bones_was_here/strafehud_jumpheight' into Juhu/strafehud-fixes
authorJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Mon, 18 Oct 2021 11:06:53 +0000 (13:06 +0200)
committerJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Mon, 18 Oct 2021 11:06:53 +0000 (13:06 +0200)
_hud_common.cfg
qcsrc/client/hud/panel/strafehud.qc
qcsrc/client/hud/panel/strafehud.qh
qcsrc/menu/xonotic/dialog_hudpanel_strafehud.qc
qcsrc/menu/xonotic/dialog_hudpanel_strafehud.qh

index 2d5aa2cfa1a374d2fbc80ab89328177984611af9..c6ccb2b82a0f11d3a40228433bbd6e6522e579f5 100644 (file)
@@ -137,38 +137,46 @@ seta hud_panel_scoreboard_itemstats_showdelay_minpos 0.75 "delay displaying the
 
 seta _hud_panel_strafehud_demo "0" "strafehud changes angle during configure"
 seta hud_panel_strafehud_mode "0" "strafehud mode which controls whether the strafehud is centered at \"0\" = view angle, \"1\" = velocity angle"
-seta hud_panel_strafehud_range "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 at once)"
-seta hud_panel_strafehud_style "1" "\"0\" = no styling, \"1\" = progress bar style for the strafe bar, \"2\" = gradient for the strafe bar"
+seta hud_panel_strafehud_range "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)"
+seta hud_panel_strafehud_style "2" "\"0\" = no styling, \"1\" = progress bar style for the strafe bar, \"2\" = gradient for the strafe bar"
 seta hud_panel_strafehud_unit "1" "speed unit (1 = qu/s, 2 = m/s, 3 = km/h, 4 = mph, 5 = knots), length unit (1 = qu, 2 = m, 3 = km, 4 = mi, 5 = nmi)"
 seta hud_panel_strafehud_unit_show "1" "show units"
+seta hud_panel_strafehud_uncapped "0" "set to \"1\" to remove some safety restrictions, useful to set thinner indicator lines down to 1px or for trying out higher values for some performance degrading operations (warning: elements may turn invisible if too thin, other configurations may crash your game or look horribly ugly)"
 seta hud_panel_strafehud_bar_neutral_color "1 1 1" "color of the strafe meter neutral zone"
-seta hud_panel_strafehud_bar_neutral_alpha "0.3" "opacity of the strafe meter neutral zone"
+seta hud_panel_strafehud_bar_neutral_alpha "0.1" "opacity of the strafe meter neutral zone"
 seta hud_panel_strafehud_bar_accel_color "0 1 0" "color of the strafe meter acceleration zone"
-seta hud_panel_strafehud_bar_accel_alpha "0.3" "opacity of the strafe meter acceleration zone"
+seta hud_panel_strafehud_bar_accel_alpha "0.5" "opacity of the strafe meter acceleration zone"
+seta hud_panel_strafehud_bar_preaccel_color "1 0.75 0" "color of the strafe meter zone before full acceleration can be achieved"
+seta hud_panel_strafehud_bar_preaccel_alpha "0.5" "opacity of the strafe meter zone before full acceleration can be achieved"
 seta hud_panel_strafehud_bar_overturn_color "1 0 1" "color of the strafe meter overturn zone"
-seta hud_panel_strafehud_bar_overturn_alpha "0.3" "opacity of the strafe meter overturn zone"
+seta hud_panel_strafehud_bar_overturn_alpha "0.5" "opacity of the strafe meter overturn zone"
+seta hud_panel_strafehud_angle_style "0" "set the angle indicator style: 0 = none, 1 = solid line, 2 = dashed line"
+seta hud_panel_strafehud_angle_dashes "4" "determines the amount of dashes if the angle indicator uses a dashed line"
 seta hud_panel_strafehud_angle_alpha "0.8" "opacity of the indicator showing the player's current angle"
-seta hud_panel_strafehud_angle_height "1.5" "height of the indicator showing the player's current angle (relative to the panel height)"
-seta hud_panel_strafehud_angle_width "0.005" "width of the indicator showing the player's current angle (relative to the panel width)"
-seta hud_panel_strafehud_angle_neutral_color "1 1 0" "color of the indicator showing the player's current angle if the player's angle is within the neutral zone"
-seta hud_panel_strafehud_angle_accel_color "0 1 1" "color of the indicator showing the player's current angle if the player's angle is within the acceleration zone"
-seta hud_panel_strafehud_angle_overturn_color "1 0 1" "color of the indicator showing the player's current angle if the player's angle is within the overturn zone"
-seta hud_panel_strafehud_switch_minspeed "-1" "minimum speed in qu/s at which switch indicators which are used to aid changing strafe direction will be shown (uses physics maxspeed + antiflicker speed if negative)"
-seta hud_panel_strafehud_switch_active_color "0 1 0" "color of the switch indicator on the current side"
-seta hud_panel_strafehud_switch_active_alpha "1" "opacity of the switch indicator on the current side"
-seta hud_panel_strafehud_switch_inactive_color "1 1 0" "color of the switch indicator on the opposite side"
-seta hud_panel_strafehud_switch_inactive_alpha "1" "opacity of the switch indicator on the opposite side"
-seta hud_panel_strafehud_switch_width "0.0075" "width of the strafe angle indicators (relative to the strafe bar width)"
+seta hud_panel_strafehud_angle_height "1" "height of the indicator showing the player's current angle (relative to the panel height)"
+seta hud_panel_strafehud_angle_width "0.001" "width of the indicator showing the player's current angle (relative to the panel width)"
+seta hud_panel_strafehud_angle_neutral_color "1 1 0" "color of the indicator showing the player's current angle if it is within the neutral zone"
+seta hud_panel_strafehud_angle_accel_color "0 1 1" "color of the indicator showing the player's current angle if it is within the acceleration zone"
+seta hud_panel_strafehud_angle_overturn_color "1 0 1" "color of the indicator showing the player's current angle if it is within the overturn zone"
+seta hud_panel_strafehud_angle_arrow "1" "set the angle indicator's arrow style: 0 = none, 1 = top, 2 = bottom, 3 = both"
+seta hud_panel_strafehud_angle_arrow_size "0.5" "size of the arrow (relative to the panel height)"
+seta hud_panel_strafehud_bestangle "0" "set to \"1\" to enable a ghost angle indicator showing the best angle to gain maximum acceleration"
+seta hud_panel_strafehud_bestangle_color "1 1 1" "color of the indicator showing the best angle to gain maximum acceleration"
+seta hud_panel_strafehud_bestangle_alpha "0.5" "opacity of the indicator showing the best angle to gain maximum acceleration"
+seta hud_panel_strafehud_switch_minspeed "-1" "minimum speed in qu/s at which switch indicator(s) which are used to aid changing strafe direction will be shown (set to -1 for dynamic minspeed)"
+seta hud_panel_strafehud_switch_color "1 1 0" "color of the switch indicator"
+seta hud_panel_strafehud_switch_alpha "1" "opacity of the switch indicator"
+seta hud_panel_strafehud_switch_width "0.003" "width of the strafe angle indicator(s) (relative to the strafe bar width)"
 seta hud_panel_strafehud_direction_color "0 0.5 1" "color of the direction caps which indicate the direction the player is currently strafing towards"
-seta hud_panel_strafehud_direction_alpha "1" "opacity of the direction caps which indicate the direction the player is currently strafing towards"
+seta hud_panel_strafehud_direction_alpha "0" "opacity of the direction caps which indicate the direction the player is currently strafing towards"
 seta hud_panel_strafehud_direction_width "0.25" "stroke width of the direction caps which indicate the direction the player is currently strafing towards (relative to the panel height)"
 seta hud_panel_strafehud_direction_length "0.02" "length of the horizontal component of the direction caps which indicate the direction the player is currently strafing towards (relative to the panel width)"
-seta hud_panel_strafehud_slickdetector_range "0" "range of the slick detector in qu, \"0\" to disable"
-seta hud_panel_strafehud_slickdetector_granularity "2" "value from 0 to 4 which defines how exact the search for slick should be, higher values may yield better results but require more computation"
+seta hud_panel_strafehud_slickdetector_range "200" "range of the slick detector in qu, \"0\" to disable"
+seta hud_panel_strafehud_slickdetector_granularity "4" "value from 0 to 4 which defines how exact the search for slick should be, higher values may yield better results but require more computation"
 seta hud_panel_strafehud_slickdetector_color "0 1 1" "color of the slick detector indicator"
 seta hud_panel_strafehud_slickdetector_alpha "0.5" "opacity of the slick detector indicator"
 seta hud_panel_strafehud_slickdetector_height "0.125" "height of the slick detector indicator (relative to the panel height)"
-seta hud_panel_strafehud_startspeed_fade "0" "fade time (in seconds) of the start speed text or \"0\" to disable"
+seta hud_panel_strafehud_startspeed_fade "4" "fade time (in seconds) of the start speed text or \"0\" to disable"
 seta hud_panel_strafehud_startspeed_color "1 0.75 0" "color of the start speed text"
 seta hud_panel_strafehud_startspeed_size "1.5" "size of the start speed text (relative to the panel height)"
 seta hud_panel_strafehud_jumpheight_fade "0" "fade time (in seconds) of the jump height text or \"0\" to disable"
@@ -178,7 +186,6 @@ seta hud_panel_strafehud_jumpheight_size "1.5" "size of the jump height text (re
 seta hud_panel_strafehud_timeout_air "0.1" "time (in seconds) after take off before changing to air strafe physics when not jumping (visually more consistent hud while on slick downwards ramps)"
 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_timeout_direction "0.5" "time (in seconds) it takes until direction changes (forward or backward movement) are applied (set to zero if you intend to sideways strafe)"
 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)"
 
index 5ddc30d2365bfd3a51bda133adcee303c7b27bda..8f050005305b4e2673afffe80cda2935cecd2780 100644 (file)
@@ -31,10 +31,9 @@ bool state_strafekeys = false;
 float state_strafekeys_time = 0;
 bool turn = false;
 float turnangle;
+float turnspeed;
+float turnaccel;
 bool fwd = true;
-bool state_fwd = true;
-bool state_fwd_prev = true;
-float state_fwd_time = 0;
 float starttime = 0;
 float startspeed = -1;
 
@@ -102,12 +101,15 @@ void HUD_StrafeHUD()
         bool   swimming                      = strafeplayer.waterlevel >= WATERLEVEL_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  maxspeed_crouch_mod           = IS_DUCKED(strafeplayer) && !swimming ? .5 : 1;
-        float  maxspeed_water_mod            = swimming ? .7 : 1; // very simplified water physics, the hud will not work well (and is not supposed to) while swimming
+        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_phys                 = onground ? PHYS_MAXSPEED(strafeplayer) : PHYS_MAXAIRSPEED(strafeplayer);
-        float  maxspeed                      = !autocvar__hud_configure ? maxspeed_phys * maxspeed_crouch_mod * maxspeed_water_mod : 320;
-        float  vel_angle                     = vectoangles(strafeplayer.velocity).y;
-        float  view_angle                    = PHYS_INPUT_ANGLES(strafeplayer).y + 180;
+        float  maxspeed                      = !autocvar__hud_configure ? maxspeed_phys * crouch_mod * water_mod : 320;
+        float  movespeed;
+        float  maxaccel_phys                 = onground ? PHYS_ACCELERATE(strafeplayer) : PHYS_AIRACCELERATE(strafeplayer);
+        float  maxaccel                      = !autocvar__hud_configure ? maxaccel_phys * crouch_mod * water_mod : 1;
+        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;
         vector movement                      = PHYS_INPUT_MOVEVALUES(strafeplayer);
         int    keys                          = STAT(PRESSED_KEYS);
@@ -132,6 +134,7 @@ void HUD_StrafeHUD()
         float  currentangle_offset;
         vector currentangle_size             = '0 0 0';
         float  bestangle;
+        float  prebestangle;
         float  odd_bestangle;
         bool   bestangle_anywhere            = false;
         float  bestangle_offset;
@@ -143,12 +146,19 @@ void HUD_StrafeHUD()
         float  accelzone_left_offset;
         float  accelzone_right_offset;
         float  accelzone_width;
+        float  preaccelzone_left_offset;
+        float  preaccelzone_right_offset;
+        float  preaccelzone_width;
         float  overturn_offset;
         float  overturn_width;
         float  slickdetector_height;
         vector direction_size_vertical       = '0 0 0';
         vector direction_size_horizontal     = '0 0 0';
         float  range_minangle;
+        float  arrow_size = max(panel_size.y * min(autocvar_hud_panel_strafehud_angle_arrow_size, 10), 0); // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing
+
+        if(!autocvar_hud_panel_strafehud_uncapped)
+            arrow_size = max(arrow_size, 1);
 
         // determine whether the player is pressing forwards or backwards keys
         if(islocal) // if entity is local player
@@ -239,7 +249,7 @@ void HUD_StrafeHUD()
             }
         }
 
-        strafekeys = fabs(wishangle) == 90;
+        strafekeys = fabs(wishangle) > 45;
 
         // determine minimum required angle to display full strafe range
         range_minangle = fabs(wishangle) % 90; // maximum range is 90 degree
@@ -279,7 +289,7 @@ void HUD_StrafeHUD()
         }
         state_strafekeys = strafekeys;
 
-        if((keys & KEY_FORWARD) || (keys & KEY_BACKWARD) || swimming || autocvar__hud_configure)
+        if((!strafekeys && vlen(vec2(movement)) > 0) || swimming || autocvar__hud_configure)
         {
             turn = false;
         }
@@ -298,6 +308,31 @@ void HUD_StrafeHUD()
                 {
                     turn = true; // CPMA turning
                     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
+                    }
+                    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
+                    }
+                    turnaccel = maxaccel;
                 }
             }
             else if((time - state_strafekeys_time) >= autocvar_hud_panel_strafehud_timeout_turn) // timeout for jumping with strafe keys only
@@ -305,13 +340,34 @@ void HUD_StrafeHUD()
                 turn = false;
             }
         }
-        if(turn)
+        if(turn && (onground || !strafekeys)) // retain last state until strafe turning times out
         {
-            maxspeed = PHYS_MAXAIRSTRAFESPEED(strafeplayer); // no modifiers here because they don't affect air strafing
             wishangle = turnangle;
+            movespeed = turnspeed;
+            maxaccel = turnaccel;
         }
+        else{
+            movespeed = vlen(vec2(movement));
+            if(movespeed == 0) movespeed = maxspeed;
+            else movespeed = min(movespeed, maxspeed);
 
-        minspeed = autocvar_hud_panel_strafehud_switch_minspeed < 0 ? maxspeed + antiflicker_speed : autocvar_hud_panel_strafehud_switch_minspeed;
+            if(onground)
+            {
+                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;
+                }
+            }
+            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;
+                }
+            }
+        }
+
+        minspeed = autocvar_hud_panel_strafehud_switch_minspeed < 0 ? (movespeed - maxaccel) + antiflicker_speed : autocvar_hud_panel_strafehud_switch_minspeed;
 
         // get current strafing angle ranging from -180° to +180°
         if(!autocvar__hud_configure)
@@ -321,29 +377,25 @@ void HUD_StrafeHUD()
                 // calculate view angle relative to the players current velocity direction
                 angle = vel_angle - view_angle;
 
-                // if the angle goes above 180° or below -180° wrap it to the opposite side
+                // if the angle goes above 180° or below -180° wrap it to the opposite side since we want the interior angle
                 if (angle > 180) angle -= 360;
                 else if(angle < -180) angle += 360;
 
-                // shift the strafe angle by 180° for hud calculations
-                if(angle < 0) angle += 180;
-                else angle -= 180;
-
                 // determine whether the player is strafing forwards or backwards
                 // if the player isn't strafe turning use forwards/backwards keys to determine direction
-                if(!strafekeys)
+                if(fabs(wishangle) != 90)
                 {
                     if(keys_fwd > 0)
                     {
-                        state_fwd = true;
+                        fwd = true;
                     }
                     else if(keys_fwd < 0)
                     {
-                        state_fwd = false;
+                        fwd = false;
                     }
                     else
                     {
-                        state_fwd = fabs(angle) <= 90;
+                        fwd = fabs(angle) <= 90;
                     }
                 }
                 // otherwise determine by examining the strafe angle
@@ -351,25 +403,14 @@ void HUD_StrafeHUD()
                 {
                     if(wishangle < 0) // detect direction using wishangle since the direction is not yet set
                     {
-                        state_fwd = angle <= -wishangle;
+                        fwd = angle <= -wishangle;
                     }
                     else
                     {
-                        state_fwd = angle >= -wishangle;
+                        fwd = angle >= -wishangle;
                     }
                 }
 
-                if(state_fwd_prev != state_fwd)
-                {
-                    state_fwd_time = time;
-                }
-                state_fwd_prev = state_fwd;
-
-                if((time - state_fwd_time) >= autocvar_hud_panel_strafehud_timeout_direction || speed < maxspeed || (strafekeys && mode == 0)) // timeout when changing between forwards and backwards movement
-                {
-                    fwd = state_fwd;
-                }
-
                 // shift the strafe angle by 180° when strafing backwards
                 if(!fwd)
                 {
@@ -428,15 +469,34 @@ void HUD_StrafeHUD()
         }
 
         // best angle to strafe at
-        bestangle = (speed > maxspeed ? acos(maxspeed / speed) : 0) * RAD2DEG * (direction < 0 ? -1 : 1);
+        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);
         odd_bestangle = -bestangle - wishangle;
         bestangle -= wishangle;
+        prebestangle -= wishangle;
 
         // various offsets and size calculations of hud indicator elements
         // how much is hidden by the current hud angle
         hidden_width = (360 - hudangle) / hudangle * panel_size.x;
         // current angle
-        currentangle_size.x = max(panel_size.x * autocvar_hud_panel_strafehud_angle_width, 1);
+        currentangle_size.x = autocvar_hud_panel_strafehud_angle_width;
+        currentangle_size.y = autocvar_hud_panel_strafehud_angle_height;
+        if(!autocvar_hud_panel_strafehud_uncapped)
+        {
+            currentangle_size.x = min(currentangle_size.x, 10);
+            currentangle_size.y = min(currentangle_size.y, 10);
+        }
+        currentangle_size.x *= panel_size.x;
+        currentangle_size.y *= panel_size.y;
+        if(!autocvar_hud_panel_strafehud_uncapped)
+        {
+            currentangle_size.x = max(currentangle_size.x, 1);
+            currentangle_size.y = max(currentangle_size.y, 1);
+        }
+        else
+        {
+            currentangle_size.y = max(currentangle_size.y, 0);
+        }
         if(mode == 0)
         {
             currentangle_offset = angle/hudangle * panel_size.x;
@@ -445,11 +505,12 @@ void HUD_StrafeHUD()
         {
             currentangle_offset = bound(-hudangle/2, angle, hudangle/2)/hudangle * panel_size.x + panel_size.x/2;
         }
-        currentangle_size.y = max(panel_size.y * min(autocvar_hud_panel_strafehud_angle_height, 2), 1);
         // best strafe acceleration angle
         bestangle_offset        =  bestangle/hudangle * panel_size.x + panel_size.x/2;
         switch_bestangle_offset = -bestangle/hudangle * panel_size.x + panel_size.x/2;
-        bestangle_width = max(panel_size.x * autocvar_hud_panel_strafehud_switch_width, 1);
+        bestangle_width = panel_size.x * autocvar_hud_panel_strafehud_switch_width;
+        if(!autocvar_hud_panel_strafehud_uncapped)
+            bestangle_width = max(bestangle_width, 1);
 
         if(((angle > -wishangle && direction < 0) || (angle < -wishangle && direction > 0)) && (direction != 0))
         {
@@ -458,9 +519,14 @@ void HUD_StrafeHUD()
             switch_odd_bestangle_offset = (odd_bestangle+bestangle*2)/hudangle * panel_size.x + panel_size.x/2;
         }
         // direction indicator
-        direction_size_vertical.x = max(panel_size.y * min(autocvar_hud_panel_strafehud_direction_width, .5), 1);
-        direction_size_vertical.y = panel_size.y;
-        direction_size_horizontal.x = max(panel_size.x * min(autocvar_hud_panel_strafehud_direction_length, .5), direction_size_vertical.x);
+        direction_size_vertical.x = autocvar_hud_panel_strafehud_direction_width;
+        if(!autocvar_hud_panel_strafehud_uncapped)
+            direction_size_vertical.x = min(direction_size_vertical.x, 1);
+        direction_size_vertical.x *= panel_size.y;
+        if(!autocvar_hud_panel_strafehud_uncapped)
+            direction_size_vertical.x = max(direction_size_vertical.x, 1);
+        direction_size_vertical.y = panel_size.y + direction_size_vertical.x*2;
+        direction_size_horizontal.x = panel_size.x * min(autocvar_hud_panel_strafehud_direction_length, .5);
         direction_size_horizontal.y = direction_size_vertical.x;
         // overturn
         overturn_width = 180/hudangle * panel_size.x;
@@ -486,23 +552,15 @@ void HUD_StrafeHUD()
         else
         {
             // calculate various zones of the strafe-o-meter
-            accelzone_width = overturn_offset = (90 - fabs(bestangle + wishangle))/hudangle * panel_size.x;
-            accelzone_right_offset = 0;
+            accelzone_width = (90 - fabs(bestangle + wishangle))/hudangle * panel_size.x;
+            preaccelzone_width = (fabs(bestangle - prebestangle))/hudangle * panel_size.x;
+            overturn_offset = accelzone_width + preaccelzone_width;
+            accelzone_right_offset = preaccelzone_width;
             accelzone_left_offset = overturn_offset + overturn_width;
-            neutral_width = 360/hudangle * panel_size.x - accelzone_width*2 - overturn_width;
-            neutral_offset = direction < 0 ? accelzone_left_offset + accelzone_width : -neutral_width;
-
-            // remove switch indicator width from offset
-            if(direction < 0)
-            {
-                bestangle_offset -= bestangle_width;
-                switch_odd_bestangle_offset -= bestangle_width;
-            }
-            else
-            {
-                switch_bestangle_offset -= bestangle_width;
-                odd_bestangle_offset -= bestangle_width;
-            }
+            preaccelzone_right_offset = 0;
+            preaccelzone_left_offset = accelzone_left_offset + accelzone_width;
+            neutral_width = 360/hudangle * panel_size.x - accelzone_width*2 - preaccelzone_width*2 - overturn_width;
+            neutral_offset = direction < 0 ? preaccelzone_left_offset + preaccelzone_width : -neutral_width;
 
             // shift hud if operating in view angle centered mode
             if(mode == 0)
@@ -520,13 +578,17 @@ void HUD_StrafeHUD()
             neutral_offset += shift_offset;
             accelzone_left_offset += shift_offset;
             accelzone_right_offset += shift_offset;
+            preaccelzone_left_offset += shift_offset;
+            preaccelzone_right_offset += shift_offset;
             overturn_offset += shift_offset;
 
             // draw left acceleration zone
             HUD_Panel_DrawStrafeHUD(accelzone_left_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, 1);
+            HUD_Panel_DrawStrafeHUD(preaccelzone_left_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 right acceleration zone
             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
             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);
@@ -534,46 +596,48 @@ void HUD_StrafeHUD()
             // draw neutral zone
             HUD_Panel_DrawStrafeHUD(neutral_offset, neutral_width, autocvar_hud_panel_strafehud_bar_neutral_color, autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 0);
 
-            if(direction != 0 && direction_size_vertical.x > 0 && autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha > 0)
+            if(speed >= minspeed && bestangle_width > 0) // only draw indicators if minspeed is reached
             {
-                bool indicator_direction = direction < 0;
-                // invert left/right when strafing backwards or when strafing towards the opposite side indicated by the direction variable
-                // if both conditions are true then it's inverted twice hence not inverted at all
-                if(!fwd != odd_angles)
+                // draw the switch indicator(s)
+                float offset = !odd_angles ? bestangle_offset : odd_bestangle_offset;
+                float switch_offset = !odd_angles ? switch_bestangle_offset : switch_odd_bestangle_offset;
+
+                // remove switch indicator width from offset
+                if(direction < 0)
                 {
-                    indicator_direction = !indicator_direction;
+                    if(!odd_angles)
+                        offset -= bestangle_width;
+                    else
+                        switch_offset -= bestangle_width;
+                }
+                else
+                {
+                    if(!odd_angles)
+                        switch_offset -= bestangle_width;
+                    else
+                        offset -= bestangle_width;
                 }
-                // draw the direction indicator caps at the sides of the hud
-                // vertical line
-                if(direction_size_vertical.y > 0) drawfill(panel_pos + eX * (indicator_direction ? -direction_size_vertical.x : panel_size.x), direction_size_vertical, autocvar_hud_panel_strafehud_direction_color, autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
-                // top horizontal line
-                drawfill(panel_pos + eX * (indicator_direction ? -direction_size_vertical.x : panel_size.x - direction_size_horizontal.x + direction_size_vertical.x) - eY * direction_size_horizontal.y, direction_size_horizontal, autocvar_hud_panel_strafehud_direction_color, autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
-                // bottom horizontal line
-                drawfill(panel_pos + eX * (indicator_direction ? -direction_size_vertical.x : panel_size.x - direction_size_horizontal.x + direction_size_vertical.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 >= minspeed) // only draw indicators if minspeed is reached
-            {
-                // draw best angles for acceleration
-                float offset = !odd_angles ? bestangle_offset : odd_bestangle_offset;
-                float switch_offset = !odd_angles ? switch_bestangle_offset : switch_odd_bestangle_offset;
-                // both indicators are inactive if no direction can be determined
-                vector switch_color = direction != 0 ? autocvar_hud_panel_strafehud_switch_active_color : autocvar_hud_panel_strafehud_switch_inactive_color;
-                float switch_alpha = direction != 0 ? autocvar_hud_panel_strafehud_switch_active_alpha : autocvar_hud_panel_strafehud_switch_inactive_alpha;
-                // draw the switch indicators
-                HUD_Panel_DrawStrafeHUD(switch_offset, bestangle_width, autocvar_hud_panel_strafehud_switch_inactive_color, autocvar_hud_panel_strafehud_switch_inactive_alpha * panel_fg_alpha, 0, 0);
-                HUD_Panel_DrawStrafeHUD(offset, bestangle_width, switch_color, switch_alpha * panel_fg_alpha, 0, 0);
+                HUD_Panel_DrawStrafeHUD(switch_offset, bestangle_width, autocvar_hud_panel_strafehud_switch_color, autocvar_hud_panel_strafehud_switch_alpha * panel_fg_alpha, 0, 0);
+                if(direction == 0) HUD_Panel_DrawStrafeHUD(offset, bestangle_width, autocvar_hud_panel_strafehud_switch_color, autocvar_hud_panel_strafehud_switch_alpha * panel_fg_alpha, 0, 0);
             }
         }
 
-        // experimental: slick detector
-        slickdetector_height = panel_size.y * bound(0, autocvar_hud_panel_strafehud_slickdetector_height, 0.5);
-        if(autocvar_hud_panel_strafehud_slickdetector_range > 0 && autocvar_hud_panel_strafehud_slickdetector_alpha > 0 && slickdetector_height > 0 && panel_size.x > 0) // dunno if slick detection works in spectate
+        // slick detector
+        slickdetector_height = max(autocvar_hud_panel_strafehud_slickdetector_height, 0);
+        if(!autocvar_hud_panel_strafehud_uncapped)
+             slickdetector_height = min(slickdetector_height, 1);
+        slickdetector_height *= panel_size.y;
+        if(autocvar_hud_panel_strafehud_slickdetector_range > 0 && autocvar_hud_panel_strafehud_slickdetector_alpha > 0 && slickdetector_height > 0 && panel_size.x > 0)
         {
-            float slicksteps = 90 / pow(2, bound(0, autocvar_hud_panel_strafehud_slickdetector_granularity, 4));
+            float slicksteps = max(autocvar_hud_panel_strafehud_slickdetector_granularity, 0);
             bool slickdetected = false;
 
-            slickdetected = IS_ONSLICK(strafeplayer); // don't need to traceline if already touching slick
+            if(!autocvar_hud_panel_strafehud_uncapped)
+                slicksteps = min(slicksteps, 4);
+            slicksteps = 90 / pow(2, slicksteps);
+
+            if(islocal) slickdetected = IS_ONSLICK(strafeplayer); // don't need to traceline if already touching slick
 
             // traceline into every direction
             trace_dphitq3surfaceflags = 0;
@@ -591,14 +655,14 @@ void HUD_StrafeHUD()
                         slickoffset.y = cos(j * DEG2RAD) * slickrotate;
 
                         traceline(strafeplayer.origin, strafeplayer.origin + slickoffset, MOVE_WORLDONLY, NULL);
-                        if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) slickdetected = true;
+                        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);
-                    if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) slickdetected = true;
+                    if((PHYS_FRICTION(strafeplayer) == 0 && trace_fraction < 1) || trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) slickdetected = true;
                 }
             }
 
@@ -614,73 +678,25 @@ void HUD_StrafeHUD()
             }
         }
 
-        draw_beginBoldFont();
-        // show speed when crossing the start trigger
-        if(autocvar_hud_panel_strafehud_startspeed_fade > 0)
+        if(direction != 0 && direction_size_vertical.x > 0 && autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha > 0)
         {
-            float text_alpha = 0;
-            if(race_checkpoint == 254) // checkpoint 254 is the start trigger
+            bool indicator_direction = direction < 0;
+            // invert left/right when strafing backwards or when strafing towards the opposite side indicated by the direction variable
+            // if both conditions are true then it's inverted twice hence not inverted at all
+            if(!fwd != odd_angles)
             {
-                if(starttime != race_checkpointtime)
-                {
-                    starttime = race_checkpointtime;
-                    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)
-            {
-                vector startspeed_size = panel_size;
-                startspeed_size.y = panel_size.y * min(autocvar_hud_panel_strafehud_startspeed_size, 5);
-                string speed_unit = GetSpeedUnit(autocvar_hud_panel_strafehud_unit);
-                drawstring_aspect(panel_pos + eY * panel_size.y, 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);
+                indicator_direction = !indicator_direction;
             }
-        }
-        else
-        {
-            starttime = 0;
-            startspeed = -1;
+            // draw the direction indicator caps at the sides of the hud
+            // vertical line
+            if(direction_size_vertical.y > 0) drawfill(panel_pos - eY * direction_size_horizontal.y + eX * (indicator_direction ? -direction_size_vertical.x : panel_size.x), direction_size_vertical, autocvar_hud_panel_strafehud_direction_color, autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+            // top horizontal line
+            drawfill(panel_pos + eX * (indicator_direction ? 0 : panel_size.x - direction_size_horizontal.x) - eY * direction_size_horizontal.y, direction_size_horizontal, autocvar_hud_panel_strafehud_direction_color, autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+            // bottom horizontal line
+            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);
         }
 
-        // show height achieved by a single jump
-        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) || IS_ONGROUND(strafeplayer) || 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;
-
-                if(jumpheight > max(autocvar_hud_panel_strafehud_jumpheight_min, 0))
-                    jumptime = time;
-            }
-
-            if((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;
-                jumpheight_size.y = panel_size.y * min(autocvar_hud_panel_strafehud_jumpheight_size, 5);
-                string length_unit = GetLengthUnit(autocvar_hud_panel_strafehud_unit);
-                drawstring_aspect(panel_pos - eY * jumpheight_size.y, strcat(ftos_decimals(jumpheight, length_decimals), autocvar_hud_panel_strafehud_unit_show ? length_unit : ""), jumpheight_size, autocvar_hud_panel_strafehud_jumpheight_color, text_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
-            }
-        }
-        draw_endBoldFont();
-
-        if(speed < (maxspeed + antiflicker_speed) && !immobile)
+        if(speed <= (fabs(movespeed - maxaccel) + antiflicker_speed) && !immobile)
         {
             bestangle_anywhere = true; // moving forward should suffice to gain speed
         }
@@ -738,10 +754,157 @@ void HUD_StrafeHUD()
             }
         }
 
-        if(currentangle_size.x > 0 && currentangle_size.y > 0 && autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha > 0)
+        float angleheight_offset = currentangle_size.y;
+        float ghost_offset = 0;
+        if(autocvar_hud_panel_strafehud_bestangle && direction != 0)
+        {
+            ghost_offset = !odd_angles ? bestangle_offset : odd_bestangle_offset;
+            if(ghost_offset < 0) ghost_offset = 0;
+            if(ghost_offset > panel_size.x) ghost_offset = panel_size.x;
+        }
+
+        switch(autocvar_hud_panel_strafehud_angle_style)
+        {
+            case 1:
+                if(currentangle_size.x > 0 && currentangle_size.y > 0)
+                {
+                    if(autocvar_hud_panel_strafehud_bestangle && direction != 0) drawfill(panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2) + eX * (ghost_offset - currentangle_size.x/2), currentangle_size, autocvar_hud_panel_strafehud_bestangle_color, autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+                    drawfill(panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2) + eX * (currentangle_offset - currentangle_size.x/2), currentangle_size, currentangle_color, autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+                }
+                break;
+            case 2:
+                if(currentangle_size.x > 0 && currentangle_size.y > 0)
+                {
+                    vector line_size = currentangle_size;
+                    line_size.y = currentangle_size.y / (bound(2, autocvar_hud_panel_strafehud_angle_dashes, currentangle_size.y)*2-1);
+                    for(float i = 0; i < currentangle_size.y; i += line_size.y*2)
+                    {
+                        if(i + line_size.y*2 >= currentangle_size.y) line_size.y = currentangle_size.y - i;
+                        if(autocvar_hud_panel_strafehud_bestangle && direction != 0) drawfill(panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2 - i) + eX * (ghost_offset - line_size.x/2), line_size, autocvar_hud_panel_strafehud_bestangle_color, autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+                        drawfill(panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2 - i) + eX * (currentangle_offset - line_size.x/2), line_size, currentangle_color, autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+                    }
+                }
+                break;
+            case 0:
+            default:
+                // don't offset text and arrows if the angle indicator line isn't drawn
+                angleheight_offset = panel_size.y;
+        }
+
+        if(autocvar_hud_panel_strafehud_angle_arrow > 0)
+        {
+            if(arrow_size > 0)
+            {
+                if(autocvar_hud_panel_strafehud_angle_arrow == 1 || autocvar_hud_panel_strafehud_angle_arrow >= 3)
+                {
+                    if(autocvar_hud_panel_strafehud_bestangle && direction != 0) StrafeHUD_drawStrafeArrow(panel_pos + eY * ((panel_size.y - angleheight_offset) / 2) + eX * ghost_offset, arrow_size, autocvar_hud_panel_strafehud_bestangle_color, autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha, true);
+                    StrafeHUD_drawStrafeArrow(panel_pos + eY * ((panel_size.y - angleheight_offset) / 2) + eX * currentangle_offset, arrow_size, currentangle_color, autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, true);
+                }
+                if(autocvar_hud_panel_strafehud_angle_arrow >= 2)
+                {
+                    if(autocvar_hud_panel_strafehud_bestangle && direction != 0) StrafeHUD_drawStrafeArrow(panel_pos + eY * ((panel_size.y - angleheight_offset) / 2 + angleheight_offset) + eX * ghost_offset, arrow_size, autocvar_hud_panel_strafehud_bestangle_color, autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha, false);
+                    StrafeHUD_drawStrafeArrow(panel_pos + eY * ((panel_size.y - angleheight_offset) / 2 + angleheight_offset) + eX * currentangle_offset, arrow_size, currentangle_color, autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, false);
+                }
+            }
+        }
+
+        draw_beginBoldFont();
+        // show speed when crossing the start trigger
+        if(autocvar_hud_panel_strafehud_startspeed_fade > 0)
+        {
+            float text_alpha = 0;
+            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)
+                {
+                    starttime = race_checkpointtime;
+                    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)
+            {
+                vector startspeed_size = panel_size;
+                startspeed_size.y = autocvar_hud_panel_strafehud_startspeed_size;
+                if(!autocvar_hud_panel_strafehud_uncapped)
+                    startspeed_size.y = min(startspeed_size.y, 10);
+                startspeed_size.y *= panel_size.y;
+                if(!autocvar_hud_panel_strafehud_uncapped)
+                    startspeed_size.y = max(startspeed_size.y, 1);
+
+                float text_offset = 0;
+                if((autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha > 0) || (autocvar_hud_panel_strafehud_bestangle && autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha > 0))
+                {
+                    text_offset = (angleheight_offset - panel_size.y) / 2;
+                    if(arrow_size > 0 && autocvar_hud_panel_strafehud_angle_arrow >= 2)
+                        text_offset += arrow_size;
+                    // make sure text doesn't draw inside the strafehud bar
+                    text_offset = max(text_offset, 0);
+                }
+
+                string speed_unit = GetSpeedUnit(autocvar_hud_panel_strafehud_unit);
+                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
+        if(autocvar_hud_panel_strafehud_jumpheight_fade > 0 && autocvar_hud_panel_strafehud_jumpheight_size > 0)
         {
-            drawfill(panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2) + eX * (currentangle_offset - currentangle_size.x/2), currentangle_size, currentangle_color, autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+            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)
+            {
+                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;
+
+                if(jumpheight > max(autocvar_hud_panel_strafehud_jumpheight_min, 0))
+                    jumptime = time;
+            }
+
+            if((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;
+                jumpheight_size.y = autocvar_hud_panel_strafehud_jumpheight_size;
+                if(!autocvar_hud_panel_strafehud_uncapped)
+                    jumpheight_size.y = min(jumpheight_size.y, 10);
+                jumpheight_size.y *= panel_size.y;
+                if(!autocvar_hud_panel_strafehud_uncapped)
+                    jumpheight_size.y = max(jumpheight_size.y, 1);
+
+                float text_offset = 0;
+                if((autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha > 0) || (autocvar_hud_panel_strafehud_bestangle && autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha > 0))
+                {
+                    text_offset = (angleheight_offset - panel_size.y) / 2;
+                    if(arrow_size > 0 && autocvar_hud_panel_strafehud_angle_arrow == 1 || autocvar_hud_panel_strafehud_angle_arrow >= 3)
+                        text_offset += arrow_size;
+                    // make sure text doesn't draw inside the strafehud bar
+                    text_offset = max(text_offset, 0);
+                }
+
+                string length_unit = GetLengthUnit(autocvar_hud_panel_strafehud_unit);
+                drawstring_aspect(panel_pos - eY * (jumpheight_size.y + text_offset), strcat(ftos_decimals(jumpheight, length_decimals), autocvar_hud_panel_strafehud_unit_show ? length_unit : ""), jumpheight_size, autocvar_hud_panel_strafehud_jumpheight_color, text_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+            }
         }
+        draw_endBoldFont();
     }
 }
 
@@ -751,9 +914,11 @@ void HUD_Panel_DrawStrafeHUD(float offset, float width, vector color, float alph
     float mirror_offset, mirror_width;
     vector size = panel_size;
     vector mirror_size = panel_size;
+    int gradient_start;
+    float gradient_offset, gradient_mirror_offset;
+    float overflow_width = 0, overflow_mirror_width = 0;
 
     float original_width = width;
-    float hiddencolor_width;
 
     if(alpha <= 0 && type != 2 || width <= 0) return;
 
@@ -771,8 +936,11 @@ void HUD_Panel_DrawStrafeHUD(float offset, float width, vector color, float alph
         mirror_width = min(offset + width - panel_size.x - hidden_width, width);
         mirror_offset = max(offset - panel_size.x - hidden_width, 0);
     }
+
+    if(width < 0) width = 0;
     if((offset + width) > panel_size.x)
     {
+        overflow_width = (offset + width) - panel_size.x;
         width = panel_size.x - offset;
     }
     if(mirror_offset < 0)
@@ -780,15 +948,14 @@ void HUD_Panel_DrawStrafeHUD(float offset, float width, vector color, float alph
         mirror_width += mirror_offset;
         mirror_offset = 0;
     }
+
+    if(mirror_width < 0) mirror_width = 0;
     if((mirror_offset + mirror_width) > panel_size.x)
     {
+        overflow_mirror_width = (mirror_offset + mirror_width) - panel_size.x;
         mirror_width = panel_size.x - mirror_offset;
     }
 
-    if(width < 0) width = 0;
-    if(mirror_width < 0) mirror_width = 0;
-    hiddencolor_width = original_width - width - mirror_width;
-
     if(direction < 0) // swap mirror and non-mirror values if direction points left
     {
         offset += mirror_offset;
@@ -798,6 +965,10 @@ void HUD_Panel_DrawStrafeHUD(float offset, float width, vector color, float alph
         width += mirror_width;
         mirror_width = width - mirror_width;
         width -= mirror_width;
+
+        overflow_width += overflow_mirror_width;
+        overflow_mirror_width = overflow_width - overflow_mirror_width;
+        overflow_width -= overflow_mirror_width;
     }
 
     size.x = width;
@@ -817,8 +988,28 @@ void HUD_Panel_DrawStrafeHUD(float offset, float width, vector color, float alph
             break;
 
         case 2: // gradient style (types: 1 = left, 2 = right, 3 = both)
-            StrafeHUD_drawGradient(color, autocvar_hud_panel_strafehud_bar_neutral_color, mirror_size, original_width, mirror_offset, alpha, width + (mirror_offset == 0 ? hiddencolor_width : 0), gradientType);
-            StrafeHUD_drawGradient(color, autocvar_hud_panel_strafehud_bar_neutral_color, size, original_width, offset, alpha, (offset == 0 ? hiddencolor_width : 0), gradientType);
+            // determine whether the gradient starts in the mirrored or the non-mirrored area
+            if(offset == 0 && mirror_offset == 0) gradient_start = width > mirror_width ? 2 : 1;
+            else if(offset == 0) gradient_start = 2;
+            else if(mirror_offset == 0) gradient_start = 1;
+            else gradient_start = 0;
+
+            switch(gradient_start){
+                default:
+                case 0: // no offset required
+                    gradient_offset = gradient_mirror_offset = 0;
+                    break;
+                case 1: // offset starts in non-mirrored area, mirrored area requires offset
+                    gradient_offset = 0;
+                    gradient_mirror_offset = original_width - (mirror_width + overflow_mirror_width);
+                    break;
+                case 2: // offset starts in mirrored area, non-mirrored area requires offset
+                    gradient_offset = original_width - (width + overflow_width);
+                    gradient_mirror_offset = 0;
+            }
+
+            StrafeHUD_drawGradient(color, autocvar_hud_panel_strafehud_bar_neutral_color, mirror_size, original_width, mirror_offset, alpha, gradient_mirror_offset, gradientType);
+            StrafeHUD_drawGradient(color, autocvar_hud_panel_strafehud_bar_neutral_color, size, original_width, offset, alpha, gradient_offset, gradientType);
     }
 }
 
@@ -856,30 +1047,41 @@ void StrafeHUD_drawGradient(vector color1, vector color2, vector size, float ori
     }
 }
 
+// draw the strafe arrows (inspired by drawspritearrow() in common/mutators/mutator/waypoints/waypointsprites.qc)
+void StrafeHUD_drawStrafeArrow(vector origin, float size, vector color, float alpha, bool flipped)
+{
+    if(flipped) origin -= size*eY;
+    R_BeginPolygon("", DRAWFLAG_NORMAL, true);
+    R_PolygonVertex(origin + (flipped ? size*eY : '0 0 0')          , '0 0 0', color, alpha);
+    R_PolygonVertex(origin + (flipped ? '0 0 0' : size*eY) - size*eX, '0 0 0', color, alpha);
+    R_PolygonVertex(origin + (flipped ? '0 0 0' : size*eY) + size*eX, '0 0 0', color, alpha);
+    R_EndPolygon();
+}
+
 // length unit conversion (km and miles are only included to match the GetSpeedUnit* functions)
 float GetLengthUnitFactor(int length_unit)
 {
-       switch(length_unit)
-       {
-               default:
-               case 1: return 1.0;
-               case 2: return 0.0254;
-               case 3: return 0.0254 * 0.001;
-               case 4: return 0.0254 * 0.001 * 0.6213711922;
-               case 5: return 0.0254 * 0.001 * 0.5399568035;
-       }
+    switch(length_unit)
+    {
+        default:
+        case 1: return 1.0;
+        case 2: return 0.0254;
+        case 3: return 0.0254 * 0.001;
+        case 4: return 0.0254 * 0.001 * 0.6213711922;
+        case 5: return 0.0254 * 0.001 * 0.5399568035;
+    }
 }
 
 string GetLengthUnit(int length_unit)
 {
-       switch(length_unit)
-       {
-               // translator-friendly strings without the initial space
-               default:
-               case 1: return strcat(" ", _("qu"));
-               case 2: return strcat(" ", _("m"));
-               case 3: return strcat(" ", _("km"));
-               case 4: return strcat(" ", _("mi"));
-               case 5: return strcat(" ", _("nmi"));
-       }
+    switch(length_unit)
+    {
+        // translator-friendly strings without the initial space
+        default:
+        case 1: return strcat(" ", _("qu"));
+        case 2: return strcat(" ", _("m"));
+        case 3: return strcat(" ", _("km"));
+        case 4: return strcat(" ", _("mi"));
+        case 5: return strcat(" ", _("nmi"));
+    }
 }
index 2fbd9b27526ca84e467cf83c23f13aff8dd8a9ef..fb438ec4170e55df05b5c58c37458fb5884b6333 100644 (file)
@@ -6,37 +6,45 @@ bool autocvar__hud_panel_strafehud_demo = false;
 bool autocvar_hud_panel_strafehud_dynamichud    = true;
 int autocvar_hud_panel_strafehud_mode = 0;
 float autocvar_hud_panel_strafehud_range = 0;
-int autocvar_hud_panel_strafehud_style = 1;
+int autocvar_hud_panel_strafehud_style = 2;
 int autocvar_hud_panel_strafehud_unit = 1;
 bool autocvar_hud_panel_strafehud_unit_show = true;
+bool autocvar_hud_panel_strafehud_uncapped = false;
 vector autocvar_hud_panel_strafehud_bar_neutral_color = '1 1 1';
-float autocvar_hud_panel_strafehud_bar_neutral_alpha = 0.3;
+float autocvar_hud_panel_strafehud_bar_neutral_alpha = 0.1;
 vector autocvar_hud_panel_strafehud_bar_accel_color = '0 1 0';
-float autocvar_hud_panel_strafehud_bar_accel_alpha = 0.3;
+float autocvar_hud_panel_strafehud_bar_accel_alpha = 0.5;
+vector autocvar_hud_panel_strafehud_bar_preaccel_color = '1 0.75 0';
+float autocvar_hud_panel_strafehud_bar_preaccel_alpha = 0.5;
 vector autocvar_hud_panel_strafehud_bar_overturn_color = '1 0 1';
-float autocvar_hud_panel_strafehud_bar_overturn_alpha = 0.3;
+float autocvar_hud_panel_strafehud_bar_overturn_alpha = 0.5;
+int autocvar_hud_panel_strafehud_angle_style = 0;
+int autocvar_hud_panel_strafehud_angle_dashes = 4;
 float autocvar_hud_panel_strafehud_angle_alpha = 0.8;
-float autocvar_hud_panel_strafehud_angle_height = 1.5;
-float autocvar_hud_panel_strafehud_angle_width = 0.005;
+float autocvar_hud_panel_strafehud_angle_height = 1;
+float autocvar_hud_panel_strafehud_angle_width = 0.001;
 vector autocvar_hud_panel_strafehud_angle_neutral_color = '1 1 0';
 vector autocvar_hud_panel_strafehud_angle_accel_color = '0 1 1';
 vector autocvar_hud_panel_strafehud_angle_overturn_color = '1 0 1';
+int autocvar_hud_panel_strafehud_angle_arrow = 1;
+float autocvar_hud_panel_strafehud_angle_arrow_size = 0.5;
+bool autocvar_hud_panel_strafehud_bestangle = false;
+vector autocvar_hud_panel_strafehud_bestangle_color = '1 1 1';
+float autocvar_hud_panel_strafehud_bestangle_alpha = 0.5;
 float autocvar_hud_panel_strafehud_switch_minspeed = -1;
-vector autocvar_hud_panel_strafehud_switch_active_color = '0 1 0';
-float autocvar_hud_panel_strafehud_switch_active_alpha = 1;
-vector autocvar_hud_panel_strafehud_switch_inactive_color = '1 1 0';
-float autocvar_hud_panel_strafehud_switch_inactive_alpha = 1;
-float autocvar_hud_panel_strafehud_switch_width = 0.0075;
+vector autocvar_hud_panel_strafehud_switch_color = '1 1 0';
+float autocvar_hud_panel_strafehud_switch_alpha = 1;
+float autocvar_hud_panel_strafehud_switch_width = 0.003;
 vector autocvar_hud_panel_strafehud_direction_color = '0 0.5 1';
-float autocvar_hud_panel_strafehud_direction_alpha = 1;
+float autocvar_hud_panel_strafehud_direction_alpha = 0;
 float autocvar_hud_panel_strafehud_direction_width = 0.25;
 float autocvar_hud_panel_strafehud_direction_length = 0.02;
-float autocvar_hud_panel_strafehud_slickdetector_range = 0;
-int autocvar_hud_panel_strafehud_slickdetector_granularity = 2;
+float autocvar_hud_panel_strafehud_slickdetector_range = 200;
+int autocvar_hud_panel_strafehud_slickdetector_granularity = 4;
 vector autocvar_hud_panel_strafehud_slickdetector_color = '0 1 1';
 float autocvar_hud_panel_strafehud_slickdetector_alpha = 0.5;
 float autocvar_hud_panel_strafehud_slickdetector_height = 0.125;
-float autocvar_hud_panel_strafehud_startspeed_fade = 0;
+float autocvar_hud_panel_strafehud_startspeed_fade = 4;
 vector autocvar_hud_panel_strafehud_startspeed_color = '1 0.75 0';
 float autocvar_hud_panel_strafehud_startspeed_size = 1.5;
 float autocvar_hud_panel_strafehud_jumpheight_fade = 0;
@@ -46,7 +54,6 @@ float autocvar_hud_panel_strafehud_jumpheight_size = 1.5;
 float autocvar_hud_panel_strafehud_timeout_air = 0.1;
 float autocvar_hud_panel_strafehud_timeout_ground = 0.03333333;
 float autocvar_hud_panel_strafehud_timeout_turn = 0.1;
-float autocvar_hud_panel_strafehud_timeout_direction = 0.5;
 float autocvar_hud_panel_strafehud_antiflicker_angle = 0.01;
 float autocvar_hud_panel_strafehud_antiflicker_speed = 0.0001;
 
@@ -55,3 +62,4 @@ vector StrafeHUD_mixColors(vector, vector, float);
 void StrafeHUD_drawGradient(vector, vector, vector, float, float, float, float, int);
 float GetLengthUnitFactor(int);
 string GetLengthUnit(int);
+void StrafeHUD_drawStrafeArrow(vector, float, vector, float, bool);
index 0344f3f1e5aa019ee7484738901ba25678e8b832..cf4c0065f6012d8717047300381a12f98f76b848 100644 (file)
@@ -23,8 +23,7 @@ void StrafeHUD_ColorReset(entity btn, entity me)
     cvar_set("hud_panel_strafehud_angle_accel_color", cvar_defstring("hud_panel_strafehud_angle_accel_color"));
     cvar_set("hud_panel_strafehud_angle_neutral_color", cvar_defstring("hud_panel_strafehud_angle_neutral_color"));
     cvar_set("hud_panel_strafehud_angle_overturn_color", cvar_defstring("hud_panel_strafehud_angle_overturn_color"));
-    cvar_set("hud_panel_strafehud_switch_active_color", cvar_defstring("hud_panel_strafehud_switch_active_color"));
-    cvar_set("hud_panel_strafehud_switch_inactive_color", cvar_defstring("hud_panel_strafehud_switch_inactive_color"));
+    cvar_set("hud_panel_strafehud_switch_color", cvar_defstring("hud_panel_strafehud_switch_color"));
     cvar_set("hud_panel_strafehud_direction_color", cvar_defstring("hud_panel_strafehud_direction_color"));
 }
 
@@ -127,24 +126,14 @@ void XonoticHUDStrafeHUDDialog_fill(entity me)
 
         me.TD(me, 1, 1.9, e = makeXonoticTextLabel(0, _("Direction caps:")));
     me.TR(me);
-        me.TD(me, 1, 0.9, e = makeXonoticTextLabel(0, _("Active:")));
-
-        me.TDempty(me, 0.2);
-
-        me.TD(me, 1, 0.9, e = makeXonoticTextLabel(0, _("Inactive:")));
-    me.TR(me);
-        me.TD(me, 2, 0.85, e = makeXonoticColorpickerString("hud_panel_strafehud_switch_active_color", "hud_panel_strafehud_switch_active_color"));
-        me.TDempty(me, 0.2);
-        me.TD(me, 2, 0.85, e = makeXonoticColorpickerString("hud_panel_strafehud_switch_inactive_color", "hud_panel_strafehud_switch_inactive_color"));
+        me.TD(me, 2, 1.9, e = makeXonoticColorpickerString("hud_panel_strafehud_switch_color", "hud_panel_strafehud_switch_color"));
 
         me.TDempty(me, 0.2);
 
         me.TD(me, 2, 1.9, e = makeXonoticColorpickerString("hud_panel_strafehud_direction_color", "hud_panel_strafehud_direction_color"));
     me.TR(me);
     me.TR(me);
-        me.TD(me, 1, 0.9, e = makeXonoticSlider(0, 1, 0.1, "hud_panel_strafehud_switch_active_alpha"));
-        me.TDempty(me, 0.1);
-        me.TD(me, 1, 0.9, e = makeXonoticSlider(0, 1, 0.1, "hud_panel_strafehud_switch_inactive_alpha"));
+        me.TD(me, 1, 1.9, e = makeXonoticSlider(0, 1, 0.1, "hud_panel_strafehud_switch_alpha"));
 
         me.TDempty(me, 0.2);
 
index f209b517be571c6346a9509d0212c9ef1e943403..50e18f9f57edf2dd5a90325e90de3e5ce6a5d1ce 100644 (file)
@@ -6,7 +6,7 @@ CLASS(XonoticHUDStrafeHUDDialog, XonoticRootDialog)
     ATTRIB(XonoticHUDStrafeHUDDialog, title, string, _("StrafeHUD Panel"));
     ATTRIB(XonoticHUDStrafeHUDDialog, color, vector, SKINCOLOR_DIALOG_TEAMSELECT);
     ATTRIB(XonoticHUDStrafeHUDDialog, intendedWidth, float, 0.4);
-    ATTRIB(XonoticHUDStrafeHUDDialog, rows, float, 23.5);
+    ATTRIB(XonoticHUDStrafeHUDDialog, rows, float, 22.5);
     ATTRIB(XonoticHUDStrafeHUDDialog, columns, float, 4);
     ATTRIB(XonoticHUDStrafeHUDDialog, name, string, "HUDstrafehud");
     ATTRIB(XonoticHUDStrafeHUDDialog, requiresConnection, float, true);