]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Juhu/strafehud-fixes
authorJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Mon, 27 Mar 2023 15:55:29 +0000 (17:55 +0200)
committerJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Mon, 27 Mar 2023 15:58:45 +0000 (17:58 +0200)
Fixed merge conflict caused by the new speed unit cvar

1  2 
_hud_common.cfg
qcsrc/client/hud/panel/strafehud.qc
qcsrc/client/hud/panel/strafehud.qh

diff --cc _hud_common.cfg
index ed53ab3ec37a9ce61f45fab82eabf5305a07a86b,6339895eafd312488b037769801bb5611e7e5854..0028a732265d8f9dfec02bf3b604ed8a38a88a27
@@@ -150,37 -156,27 +156,36 @@@ seta hud_panel_scoreboard_itemstats_sho
  
  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 "90" "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_preaccel "1" "set to \"1\" to extend the acceleration zone by the strafe meter zone before full acceleration can be achieved"
  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_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 "1" "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 "1" "set to \"1\" to enable the switch indicator showing the angle to move to when switching sides"
 +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 "0" "set to \"1\" to enable the direction caps to see in which direction you are currently strafing"
  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_width "0.25" "stroke width of the direction caps which indicate the direction the player is currently strafing towards (relative to the panel height)"
index c82a6c779ee53065cf289e339c3ca0ec036797f1,46e8b7631b4eb7d3529f622c491cf9222ae00e86..075dfb3711bed22b957f3723c44e65ae06865f56
@@@ -32,1059 -51,698 +32,1059 @@@ float GeomLerp(float a, float _lerp, fl
  
  void HUD_StrafeHUD()
  {
 -    entity strafeplayer;
 -    bool islocal;
 -
 -    // generic hud routines
 -    if(!autocvar__hud_configure)
 -    {
 -        if(!autocvar_hud_panel_strafehud ||
 -           (spectatee_status == -1 && (autocvar_hud_panel_strafehud == 1 || autocvar_hud_panel_strafehud == 3)) ||
 -           (autocvar_hud_panel_strafehud == 3 && !MUTATOR_CALLHOOK(HUD_StrafeHUD_showoptional))) return;
 -    }
 -
 -    HUD_Panel_LoadCvars();
 -
 -    if(autocvar_hud_panel_strafehud_dynamichud)
 -    {
 -        HUD_Scale_Enable();
 -    }
 -    else
 -    {
 -        HUD_Scale_Disable();
 -    }
 -
 -    HUD_Panel_DrawBg();
 -
 -    if(panel_bg_padding)
 -    {
 -        panel_pos  += '1 1 0' * panel_bg_padding;
 -        panel_size -= '2 2 0' * panel_bg_padding;
 -    }
 -
 -    // find out whether the local csqcmodel entity is valid
 -    if(spectatee_status > 0 || isdemo())
 -    {
 -        islocal = false;
 -        strafeplayer = CSQCModel_server2csqc(player_localentnum - 1);
 -    }
 -    else
 -    {
 -        islocal = true;
 -        strafeplayer = csqcplayer;
 -    }
 -
 -    // draw strafehud
 -    if(csqcplayer && strafeplayer)
 -    {
 -        // physics
 -        bool   onground                      = islocal ? IS_ONGROUND(strafeplayer) : !(strafeplayer.anim_implicit_state & ANIMIMPLICITSTATE_INAIR);
 -        bool   strafekeys;
 -        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  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  angle;
 -        vector movement                      = PHYS_INPUT_MOVEVALUES(strafeplayer);
 -        int    keys                          = STAT(PRESSED_KEYS);
 -        int    keys_fwd;
 -        float  wishangle                     = 0;
 -
 -        // HUD
 -        int    mode                          = autocvar_hud_panel_strafehud_mode >= 0 && autocvar_hud_panel_strafehud_mode <= 1 ? autocvar_hud_panel_strafehud_mode : 0;
 -        float  speed_conversion_factor       = GetSpeedUnitFactor(autocvar_hud_speed_unit);
 -        float  length_conversion_factor      = GetLengthUnitFactor(autocvar_hud_speed_unit);
 -        int    length_decimals               = autocvar_hud_speed_unit >= 3 && autocvar_hud_speed_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);
 -        float  hudangle;
 -        float  neutral_offset;
 -        float  neutral_width;
 -        vector currentangle_color            = autocvar_hud_panel_strafehud_angle_neutral_color;
 -        float  currentangle_offset;
 -        vector currentangle_size             = '0 0 0';
 -        float  bestangle;
 -        float  odd_bestangle;
 -        bool   bestangle_anywhere            = false;
 -        float  bestangle_offset;
 -        float  switch_bestangle_offset;
 -        bool   odd_angles                    = false;
 -        float  odd_bestangle_offset          = 0;
 -        float  switch_odd_bestangle_offset   = 0;
 -        float  bestangle_width;
 -        float  accelzone_left_offset;
 -        float  accelzone_right_offset;
 -        float  accelzone_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;
 -
 -        // determine whether the player is pressing forwards or backwards keys
 -        if(islocal) // if entity is local player
 -        {
 -            if(movement.x > 0)
 -            {
 -                keys_fwd = 1;
 -            }
 -            else if(movement.x < 0)
 -            {
 -                keys_fwd = -1;
 -            }
 -            else
 -            {
 -                keys_fwd = 0;
 -            }
 -        }
 -        else // alternatively determine direction by querying pressed keys
 -        {
 -            if((keys & KEY_FORWARD) && !(keys & KEY_BACKWARD))
 -            {
 -                keys_fwd = 1;
 -            }
 -            else if(!(keys & KEY_FORWARD) && (keys & KEY_BACKWARD))
 -            {
 -                keys_fwd = -1;
 -            }
 -            else
 -            {
 -                keys_fwd = 0;
 -            }
 -        }
 -
 -        // determine player wishdir
 -        if(islocal) // if entity is local player
 -        {
 -            if(movement.x == 0)
 -            {
 -                if(movement.y < 0)
 -                {
 -                    wishangle = -90;
 -                }
 -                else if(movement.y > 0)
 -                {
 -                    wishangle = 90;
 -                }
 -                else
 -                {
 -                    wishangle = 0;
 -                }
 -            }
 -            else
 -            {
 -                if(movement.y == 0)
 -                {
 -                    wishangle = 0;
 -                }
 -                else
 -                {
 -                    wishangle = RAD2DEG * atan2(movement.y, movement.x);
 -                    // wrap the wish angle if it exceeds ±90°
 -                    if(fabs(wishangle) > 90)
 -                    {
 -                        if(wishangle < 0) wishangle += 180;
 -                        else wishangle -= 180;
 -                        wishangle = -wishangle;
 -                    }
 -                }
 -            }
 -        }
 -        else // alternatively calculate wishdir by querying pressed keys
 -        {
 -            if(keys & KEY_FORWARD || keys & KEY_BACKWARD)
 -            {
 -                wishangle = 45;
 -            }
 -            else
 -            {
 -                wishangle = 90;
 -            }
 -            if(keys & KEY_LEFT)
 -            {
 -                wishangle *= -1;
 -            }
 -            else if(!(keys & KEY_RIGHT))
 -            {
 -                wishangle = 0; // wraps at 180°
 -            }
 -        }
 -
 -        strafekeys = fabs(wishangle) == 90;
 -
 -        // determine minimum required angle to display full strafe range
 -        range_minangle = fabs(wishangle) % 90; // maximum range is 90 degree
 -        if(range_minangle > 45) // minimum angle range is 45
 -        {
 -            range_minangle = 45 - fabs(wishangle) % 45;
 -        }
 -        range_minangle = 90 - range_minangle; // calculate value which is never >90 or <45
 -        range_minangle *= 2; // multiply to accommodate for both sides of the hud
 -
 -        if(autocvar_hud_panel_strafehud_range == 0)
 -        {
 -            if(autocvar__hud_configure)
 -            {
 -                hudangle = 90;
 -            }
 -            else
 -            {
 -                hudangle = range_minangle; // use minimum angle required if dynamically setting hud angle
 -            }
 -        }
 -        else
 -        {
 -            hudangle = bound(0, fabs(autocvar_hud_panel_strafehud_range), 360); // limit HUD range to 360 degrees, higher values don't make sense
 -        }
 -
 -        // detect air strafe turning
 -        if(onground != state_onground)
 -        {
 -            state_onground_time = time;
 -        }
 -        state_onground = onground;
 -
 -        if(strafekeys != state_strafekeys)
 -        {
 -            state_strafekeys_time = time;
 -        }
 -        state_strafekeys = strafekeys;
 -
 -        if((keys & KEY_FORWARD) || (keys & KEY_BACKWARD) || swimming || autocvar__hud_configure)
 -        {
 -            turn = false;
 -        }
 -        else if(onground)
 -        {
 -            if((time - state_onground_time) >= autocvar_hud_panel_strafehud_timeout_ground) // timeout for strafe jumping in general
 -            {
 -                turn = false;
 -            }
 -        }
 -        else // air strafe only
 -        {
 -            if(strafekeys)
 -            {
 -                if(((time - state_onground_time) >= autocvar_hud_panel_strafehud_timeout_air) || (keys & KEY_JUMP)) // timeout for slick ramps
 -                {
 -                    turn = true; // CPMA turning
 -                    turnangle = wishangle;
 -                }
 -            }
 -            else if((time - state_strafekeys_time) >= autocvar_hud_panel_strafehud_timeout_turn) // timeout for jumping with strafe keys only
 -            {
 -                turn = false;
 -            }
 -        }
 -        if(turn)
 -        {
 -            maxspeed = PHYS_MAXAIRSTRAFESPEED(strafeplayer); // no modifiers here because they don't affect air strafing
 -            wishangle = turnangle;
 -        }
 -
 -        minspeed = autocvar_hud_panel_strafehud_switch_minspeed < 0 ? maxspeed + antiflicker_speed : autocvar_hud_panel_strafehud_switch_minspeed;
 -
 -        // get current strafing angle ranging from -180° to +180°
 -        if(!autocvar__hud_configure)
 -        {
 -            if(speed > 0)
 -            {
 -                // 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 (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(keys_fwd > 0)
 -                    {
 -                        state_fwd = true;
 -                    }
 -                    else if(keys_fwd < 0)
 -                    {
 -                        state_fwd = false;
 -                    }
 -                    else
 -                    {
 -                        state_fwd = fabs(angle) <= 90;
 -                    }
 -                }
 -                // otherwise determine by examining the strafe angle
 -                else
 -                {
 -                    if(wishangle < 0) // detect direction using wishangle since the direction is not yet set
 -                    {
 -                        state_fwd = angle <= -wishangle;
 -                    }
 -                    else
 -                    {
 -                        state_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)
 -                {
 -                    if(angle < 0) angle += 180;
 -                    else angle -= 180;
 -                }
 -
 -                // don't make the angle indicator switch side too much at ±180° if anti flicker is turned on
 -                if(angle > (180 - antiflicker_angle) || angle < (-180 + antiflicker_angle))
 -                {
 -                    straight_overturn = true;
 -                }
 -            }
 -            else
 -            {
 -                angle = 0;
 -            }
 -        }
 -        else // simulate turning for HUD setup
 -        {
 -            fwd = true;
 -            if(autocvar__hud_panel_strafehud_demo && ((time - demo_time) >= .025))
 -            {
 -                demo_time = time;
 -                demo_angle += demo_direction;
 -                if(fabs(demo_angle) >= 55)
 -                {
 -                    demo_direction = -demo_direction;
 -                }
 -            }
 -            angle = demo_angle;
 -            wishangle = 45 * (demo_angle > 0 ? 1 : -1);
 -        }
 -
 -        // invert the wish angle when strafing backwards
 -        if(!fwd)
 -        {
 -            wishangle = -wishangle;
 -        }
 -
 -        // flip angles if v_flipped is enabled
 -        if(autocvar_v_flipped)
 -        {
 -            angle = -angle;
 -            wishangle = -wishangle;
 -        }
 -
 -        // determine whether the player is strafing left or right
 -        if(wishangle != 0)
 -        {
 -            direction = wishangle > 0 ? 1 : -1;
 -        }
 -        else
 -        {
 -            direction = (angle > antiflicker_angle && angle < (180 - antiflicker_angle)) ? 1 : (angle < -antiflicker_angle && angle > (-180 + antiflicker_angle)) ? -1 : 0;
 -        }
 -
 -        // best angle to strafe at
 -        bestangle = (speed > maxspeed ? acos(maxspeed / speed) : 0) * RAD2DEG * (direction < 0 ? -1 : 1);
 -        odd_bestangle = -bestangle - wishangle;
 -        bestangle -= 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);
 -        if(mode == 0)
 -        {
 -            currentangle_offset = angle/hudangle * panel_size.x;
 -        }
 -        else
 -        {
 -            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);
 -
 -        if(((angle > -wishangle && direction < 0) || (angle < -wishangle && direction > 0)) && (direction != 0))
 -        {
 -            odd_angles = true;
 -            odd_bestangle_offset = odd_bestangle/hudangle * panel_size.x + panel_size.x/2;
 -            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_horizontal.y = direction_size_vertical.x;
 -        // overturn
 -        overturn_width = 180/hudangle * panel_size.x;
 -
 -        // the neutral zone fills the whole strafe bar
 -        if(immobile)
 -        {
 -            // draw neutral zone
 -            if(panel_size.x > 0 && panel_size.y > 0 && autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha > 0)
 -            {
 -                switch(autocvar_hud_panel_strafehud_style)
 -                {
 -                    default:
 -                    case 0:
 -                        drawfill(panel_pos, panel_size, autocvar_hud_panel_strafehud_bar_neutral_color, autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 -                        break;
 -
 -                    case 1:
 -                        HUD_Panel_DrawProgressBar(panel_pos, panel_size, "progressbar", 1, 0, 0, autocvar_hud_panel_strafehud_bar_neutral_color, autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 -                }
 -            }
 -        }
 -        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_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;
 -            }
 -
 -            // shift hud if operating in view angle centered mode
 -            if(mode == 0)
 -            {
 -                shift_offset = -currentangle_offset;
 -                bestangle_offset += shift_offset;
 -                switch_bestangle_offset += shift_offset;
 -                odd_bestangle_offset += shift_offset;
 -                switch_odd_bestangle_offset += shift_offset;
 -            }
 -            if(direction < 0) shift_offset += -360/hudangle * panel_size.x;
 -            // calculate how far off-center the strafe zones currently are
 -            shift_offset += (panel_size.x + neutral_width)/2 - wishangle/hudangle * panel_size.x;
 -            // shift strafe zones into correct place
 -            neutral_offset += shift_offset;
 -            accelzone_left_offset += shift_offset;
 -            accelzone_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);
 -
 -            // 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);
 -
 -            // 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);
 -
 -            // 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)
 -            {
 -                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)
 -                {
 -                    indicator_direction = !indicator_direction;
 -                }
 -                // 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);
 -            }
 -        }
 -
 -        // 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
 -        {
 -            float slicksteps = 90 / pow(2, bound(0, autocvar_hud_panel_strafehud_slickdetector_granularity, 4));
 -            bool slickdetected = false;
 -
 -            slickdetected = IS_ONSLICK(strafeplayer); // don't need to traceline if already touching slick
 -
 -            // traceline into every direction
 -            trace_dphitq3surfaceflags = 0;
 -            for(float i = 0; i < 360 && !slickdetected; i += slicksteps)
 -            {
 -                vector slickoffset;
 -                float slickrotate;
 -                slickoffset.z = -cos(i * DEG2RAD) * autocvar_hud_panel_strafehud_slickdetector_range;
 -                slickrotate = sin(i * DEG2RAD) * autocvar_hud_panel_strafehud_slickdetector_range;
 -                if(i != 0 && i != 180)
 -                {
 -                    for(float j = 0; j < 180 && !slickdetected; j += slicksteps)
 -                    {
 -                        slickoffset.x = sin(j * DEG2RAD) * slickrotate;
 -                        slickoffset.y = cos(j * DEG2RAD) * slickrotate;
 -
 -                        traceline(strafeplayer.origin, strafeplayer.origin + slickoffset, MOVE_WORLDONLY, NULL);
 -                        if(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 a traceline hit a slick surface
 -            if(slickdetected)
 -            {
 -                vector slickdetector_size = panel_size;
 -                slickdetector_size.y = slickdetector_height;
 -                // top horizontal line
 -                drawfill(panel_pos - eY * slickdetector_size.y, slickdetector_size, autocvar_hud_panel_strafehud_slickdetector_color, autocvar_hud_panel_strafehud_slickdetector_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 -                // bottom horizontal line
 -                drawfill(panel_pos + eY * panel_size.y, slickdetector_size, autocvar_hud_panel_strafehud_slickdetector_color, autocvar_hud_panel_strafehud_slickdetector_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 -            }
 -        }
 -
 -        draw_beginBoldFont();
 -        // show speed when crossing the start trigger
 -        if(autocvar_hud_panel_strafehud_startspeed_fade > 0)
 -        {
 -            float text_alpha = 0;
 -            if(race_checkpoint == 254) // checkpoint 254 is the start trigger
 -            {
 -                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_speed_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);
 -            }
 -        }
 -        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)
 -        {
 -            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_speed_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)
 -        {
 -            bestangle_anywhere = true; // moving forward should suffice to gain speed
 -        }
 -
 -        // draw the actual strafe angle
 -        if(!bestangle_anywhere && !immobile) // player gains speed with strafing
 -        {
 -            if((direction > 0 && (angle >= bestangle || angle <= -(bestangle + wishangle*2))) ||
 -               (direction < 0 && (angle <= bestangle || angle >= -(bestangle + wishangle*2))))
 -            currentangle_color = autocvar_hud_panel_strafehud_angle_accel_color;
 -        }
 -
 -        if(fabs(angle + wishangle) > 90) // player is overturning
 -        {
 -            currentangle_color = autocvar_hud_panel_strafehud_angle_overturn_color;
 -        }
 -        else if(bestangle_anywhere) // player gains speed without strafing
 -        {
 -            currentangle_color = autocvar_hud_panel_strafehud_angle_accel_color;
 -        }
 -
 -        if(mode == 0 || straight_overturn)
 -        {
 -            currentangle_offset = panel_size.x/2;
 -        }
 -
 -        if(autocvar_hud_panel_strafehud_style == 2 && !immobile)
 -        {
 -            float moveangle = angle + wishangle;
 -            float strafeangle = (bestangle + wishangle) * (direction < 0 ? -1 : 1);
 -            float strafe_ratio = 0;
 -            if(fabs(moveangle) > 90)
 -            {
 -                strafe_ratio = -((fabs(moveangle) - 90) / 90);
 -                if(strafe_ratio < -1) strafe_ratio = -2 - strafe_ratio;
 -            }
 -            else
 -            {
 -                if(moveangle >= strafeangle)
 -                {
 -                    strafe_ratio = 1 - (moveangle - strafeangle) / (90 - strafeangle);
 -                }
 -                else if(moveangle <= -strafeangle)
 -                {
 -                    strafe_ratio = 1 - (moveangle + strafeangle) / (-90 + strafeangle);
 -                }
 -            }
 -            if(strafe_ratio < 0)
 -            {
 -                currentangle_color = StrafeHUD_mixColors(autocvar_hud_panel_strafehud_angle_neutral_color, autocvar_hud_panel_strafehud_angle_overturn_color, -strafe_ratio);
 -            }
 -            else
 -            {
 -                currentangle_color = StrafeHUD_mixColors(autocvar_hud_panel_strafehud_angle_neutral_color, autocvar_hud_panel_strafehud_angle_accel_color, strafe_ratio);
 -            }
 -        }
 -
 -        if(currentangle_size.x > 0 && currentangle_size.y > 0 && autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha > 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 hud_lasttime = 0;
 +      entity strafeplayer;
 +      bool islocal;
 +
 +      // generic hud routines
 +      if(!autocvar__hud_configure)
 +      {
 +              if(!autocvar_hud_panel_strafehud ||
 +                 (spectatee_status == -1 && (autocvar_hud_panel_strafehud == 1 || autocvar_hud_panel_strafehud == 3)) ||
 +                 (autocvar_hud_panel_strafehud == 3 && !MUTATOR_CALLHOOK(HUD_StrafeHUD_showoptional))) { hud_lasttime = time; return; }
 +      }
 +
 +      HUD_Panel_LoadCvars();
 +
 +      if(autocvar_hud_panel_strafehud_dynamichud)
 +              HUD_Scale_Enable();
 +      else
 +              HUD_Scale_Disable();
 +
 +      HUD_Panel_DrawBg();
 +
 +      if(panel_bg_padding)
 +      {
 +              panel_pos  += '1 1 0' * panel_bg_padding;
 +              panel_size -= '2 2 0' * panel_bg_padding;
 +      }
 +
 +      // find out whether the local csqcmodel entity is valid
 +      if(spectatee_status > 0 || isdemo())
 +      {
 +              islocal = false;
 +              strafeplayer = CSQCModel_server2csqc(player_localentnum - 1);
 +      }
 +      else
 +      {
 +              islocal = true;
 +              strafeplayer = csqcplayer;
 +      }
 +
 +      // draw strafehud
 +      if(csqcplayer && strafeplayer)
 +      {
 +              float strafe_waterlevel;
 +
 +              // check the player waterlevel without affecting the player entity, this way we can fetch waterlevel even if client prediction is disabled
 +              {
 +                      // 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;
 +              }
 +
 +              int keys = STAT(PRESSED_KEYS);
 +              // try to ignore if track_canjump is enabled, doesn't work in spectator mode if spectated player uses +jetpack or cl_movement_track_canjump
 +              bool jumpheld = false;
 +              if(islocal)
 +              {
 +                      if((PHYS_INPUT_BUTTON_JUMP(strafeplayer) || PHYS_INPUT_BUTTON_JETPACK(strafeplayer)) && !PHYS_CL_TRACK_CANJUMP(strafeplayer))
 +                              jumpheld = true;
 +              }
 +              else
 +              {
 +                      if((keys & KEY_JUMP) && !PHYS_TRACK_CANJUMP(strafeplayer))
 +                              jumpheld = true;
 +              }
 +
 +              // persistent
 +              static float onground_lasttime       = 0;
 +              static bool  onslick_last            = false;
 +              static float turn_lasttime           = 0;
 +              static bool  turn                    = false;
 +              static float turnangle;
 +              static float dt_update               = 0;
 +              static int   dt_time                 = 0;
 +              static float dt_sum                  = 0;
 +              static float dt                      = 0;
 +
 +              // physics
 +              // doesn't get changed by ground timeout and isn't affected by jump input
 +              bool   real_onground                 = islocal ? IS_ONGROUND(strafeplayer) : !(strafeplayer.anim_implicit_state & ANIMIMPLICITSTATE_INAIR);
 +              // doesn't get changed by ground timeout
 +              bool   real_onslick                  = false;
 +              // if jump is held assume we are in air, avoids flickering of the hud when hitting the ground
 +              bool   onground                      = real_onground && !jumpheld;
 +              bool   onslick                       = real_onslick;
 +              bool   onground_expired;
 +              bool   strafekeys;
 +              // the hud will not work well while swimming
 +              bool   swimming                      = strafe_waterlevel >= WATERLEVEL_SWIMMING;
 +              // use local csqcmodel entity for this even when spectating, flickers too much otherwise
 +              float  speed                         = !autocvar__hud_configure ? vlen(vec2(csqcplayer.velocity)) : 1337;
 +              // only the local csqcplayer entity contains this information even when spectating
 +              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 * maxspeed_mod : 320;
 +              float  movespeed;
 +              float  bestspeed;
 +              float  maxaccel_phys                 = onground ? PHYS_ACCELERATE(strafeplayer) : PHYS_AIRACCELERATE(strafeplayer);
 +              float  maxaccel                      = !autocvar__hud_configure ? maxaccel_phys : 1;
 +              // change the range from 0° - 360° to -180° - 180° to match how view_angle represents angles
 +              float  vel_angle                     = vectoangles(strafeplayer.velocity).y - (vectoangles(strafeplayer.velocity).y > 180 ? 360 : 0);
 +              float  view_angle                    = PHYS_INPUT_ANGLES(strafeplayer).y;
 +              float  angle;
 +              vector movement                      = PHYS_INPUT_MOVEVALUES(strafeplayer);
 +              bool   fwd;
 +              int    keys_fwd;
 +              float  wishangle;
 +              int    direction;
 +
 +              // HUD
 +              int    mode;
-               float  speed_conversion_factor       = GetSpeedUnitFactor(autocvar_hud_panel_strafehud_unit);
-               float  length_conversion_factor      = GetLengthUnitFactor(autocvar_hud_panel_strafehud_unit);
++              float  speed_conversion_factor       = GetSpeedUnitFactor(autocvar_hud_speed_unit);
++              float  length_conversion_factor      = GetLengthUnitFactor(autocvar_hud_speed_unit);
 +              // use more decimals when displaying km or miles
-               int    length_decimals               = autocvar_hud_panel_strafehud_unit >= 3 && autocvar_hud_panel_strafehud_unit <= 5 ? 6 : 2;
++              int    length_decimals               = autocvar_hud_speed_unit >= 3 && autocvar_hud_speed_unit <= 5 ? 6 : 2;
 +              float  antiflicker_angle             = bound(0, autocvar_hud_panel_strafehud_antiflicker_angle, 180);
 +              float  minspeed;
 +              float  shift_offset                  = 0;
 +              bool   straight_overturn             = false;
 +              bool   immobile                      = speed <= 0;
 +              float  hudangle;
 +              float  hidden_width;
 +              float  neutral_offset;
 +              float  neutral_width;
 +              vector currentangle_color            = autocvar_hud_panel_strafehud_angle_neutral_color;
 +              float  currentangle_offset;
 +              vector currentangle_size;
 +              float  bestangle;
 +              float  prebestangle;
 +              float  odd_bestangle;
 +              float  bestangle_offset;
 +              float  switch_bestangle_offset;
 +              bool   odd_angles                    = false;
 +              float  odd_bestangle_offset          = 0;
 +              float  switch_odd_bestangle_offset   = 0;
 +              float  bestangle_width;
 +              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;
 +              vector direction_size_horizontal;
 +              float  range_minangle;
 +              float  text_offset_top               = 0;
 +              float  text_offset_bottom            = 0;
 +
 +              // real_* variables which are always positive with no wishangle offset
 +              float real_bestangle;
 +              float real_prebestangle;
 +
 +              if(autocvar_hud_panel_strafehud_mode >= 0 && autocvar_hud_panel_strafehud_mode <= 1)
 +                      mode = autocvar_hud_panel_strafehud_mode;
 +              else
 +                      mode = STRAFEHUD_MODE_VIEW_CENTERED;
 +
 +              // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing
 +              float arrow_size = max(panel_size.y * min(autocvar_hud_panel_strafehud_angle_arrow_size, 10), 0);
 +
 +              if(onground)
 +              {
 +                      if(PHYS_FRICTION(strafeplayer) == 0)
 +                      {
 +                              onslick = true;
 +                      }
 +                      else // don't use IS_ONSLICK(), it only works for the local player and only if client prediction is enabled
 +                      {
 +                              trace_dphitq3surfaceflags = 0;
 +                              tracebox(strafeplayer.origin, strafeplayer.mins, strafeplayer.maxs, strafeplayer.origin - '0 0 1', MOVE_NOMONSTERS, strafeplayer);
 +                              onslick = trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK;
 +                      }
 +                      real_onslick = onslick;
 +
 +                      onground_lasttime = time;
 +                      onslick_last = onslick;
 +              }
 +              else if(jumpheld || swimming)
 +              {
 +                      onground_lasttime = 0;
 +              }
 +
 +              if(onground_lasttime == 0)
 +                      onground_expired = true;
 +              else
 +                      onground_expired = (time - onground_lasttime) >= autocvar_hud_panel_strafehud_timeout_ground; // timeout for slick ramps
 +
 +              if(!onground && !onground_expired) // if ground timeout hasn't expired yet use ground physics
 +              {
 +                      onground = true;
 +                      onslick = onslick_last;
 +
 +                      if(!autocvar__hud_configure)
 +                      {
 +                              maxspeed = PHYS_MAXSPEED(strafeplayer) * maxspeed_mod;
 +                              maxaccel = PHYS_ACCELERATE(strafeplayer);
 +                      }
 +              }
 +
 +              movespeed = vlen(vec2(movement));
 +              if(movespeed == 0)
 +                      movespeed = maxspeed;
 +              else
 +                      movespeed = min(movespeed, maxspeed);
 +
 +              if(!autocvar_hud_panel_strafehud_uncapped)
 +                      arrow_size = max(arrow_size, 1);
 +
 +              // determine frametime
 +              if((csqcplayer_status == CSQCPLAYERSTATUS_PREDICTED) && (input_timelength > 0))
 +              {
 +                      float dt_client = input_timelength;
 +
 +                      if(dt_client > .05) // server splits frames longer than 50 ms into two moves
 +                              dt_client /= 2; // doesn't ensure frames are smaller than 50 ms, just splits large frames in half, matches server behaviour
 +
 +                      // calculate average frametime
 +                      dt_sum += dt_client * dt_client;
 +                      dt_time += dt_client;
 +
 +                      if(((time - dt_update) > autocvar_hud_panel_strafehud_fps_update) || (dt_update == 0))
 +                      {
 +                              dt = dt_sum / dt_time;
 +                              dt_update = time;
 +                              dt_time = dt_sum = 0;
 +                      }
 +              }
 +              else // when spectating other players server ticrate will be used, this may not be accurate but there is no way to find other player's frametime
 +              {
 +                      dt = ticrate;
 +                      dt_update = dt_time = dt_sum = 0;
 +              }
 +
 +              // determine whether the player is pressing forwards or backwards keys
 +              if(islocal) // if entity is local player
 +              {
 +                      if(movement.x > 0)
 +                              keys_fwd = STRAFEHUD_KEYS_FORWARD;
 +                      else if(movement.x < 0)
 +                              keys_fwd = STRAFEHUD_KEYS_BACKWARD;
 +                      else
 +                              keys_fwd = STRAFEHUD_KEYS_NONE;
 +              }
 +              else // alternatively determine direction by querying pressed keys
 +              {
 +                      if((keys & KEY_FORWARD) && !(keys & KEY_BACKWARD))
 +                              keys_fwd = STRAFEHUD_KEYS_FORWARD;
 +                      else if(!(keys & KEY_FORWARD) && (keys & KEY_BACKWARD))
 +                              keys_fwd = STRAFEHUD_KEYS_BACKWARD;
 +                      else
 +                              keys_fwd = STRAFEHUD_KEYS_NONE;
 +              }
 +
 +              // determine player wishdir
 +              if(islocal) // if entity is local player
 +              {
 +                      if(movement.x == 0)
 +                      {
 +                              if(movement.y < 0)
 +                                      wishangle = -90;
 +                              else if(movement.y > 0)
 +                                      wishangle = 90;
 +                              else
 +                                      wishangle = 0;
 +                      }
 +                      else
 +                      {
 +                              if(movement.y == 0)
 +                              {
 +                                      wishangle = 0;
 +                              }
 +                              else
 +                              {
 +                                      wishangle = RAD2DEG * atan2(movement.y, movement.x);
 +                                      // wrap the wish angle if it exceeds ±90°
 +                                      if(fabs(wishangle) > 90)
 +                                      {
 +                                              if(wishangle < 0)
 +                                                      wishangle += 180;
 +                                              else
 +                                                      wishangle -= 180;
 +
 +                                              wishangle *= -1;
 +                                      }
 +                              }
 +                      }
 +              }
 +              else // alternatively calculate wishdir by querying pressed keys
 +              {
 +                      if(keys & KEY_FORWARD || keys & KEY_BACKWARD)
 +                              wishangle = 45;
 +                      else
 +                              wishangle = 90;
 +                      if(keys & KEY_LEFT)
 +                              wishangle *= -1;
 +                      else if(!(keys & KEY_RIGHT))
 +                              wishangle = 0; // wraps at 180°
 +              }
 +
 +              strafekeys = fabs(wishangle) > 45;
 +
 +              // determine minimum required angle to display full strafe range
 +              range_minangle = fabs(wishangle) % 90; // maximum range is 90 degree
 +              if(range_minangle > 45) range_minangle = 45 - fabs(wishangle) % 45; // minimum angle range is 45
 +              range_minangle = 90 - range_minangle; // calculate value which is never >90 or <45
 +              range_minangle *= 2; // multiply to accommodate for both sides of the hud
 +
 +              if(autocvar_hud_panel_strafehud_range == 0)
 +              {
 +                      if(autocvar__hud_configure)
 +                              hudangle = 90;
 +                      else
 +                              hudangle = range_minangle; // use minimum angle required if dynamically setting hud angle
 +              }
 +              else
 +              {
 +                      hudangle = bound(0, fabs(autocvar_hud_panel_strafehud_range), 360); // limit HUD range to 360 degrees, higher values don't make sense
 +              }
 +
 +              // detect air strafe turning
 +              if((!strafekeys && vlen(vec2(movement)) > 0) || onground || autocvar__hud_configure)
 +              {
 +                      turn = false;
 +              }
 +              else // air strafe only
 +              {
 +                      bool turn_expired = (time - turn_lasttime) >= autocvar_hud_panel_strafehud_timeout_turn; // timeout for jumping with strafe keys only
 +
 +                      if(strafekeys)
 +                              turn = true;
 +                      else if(turn_expired)
 +                              turn = false;
 +
 +                      if(turn) // CPMA turning
 +                      {
 +                              if(strafekeys)
 +                              {
 +                                      turn_lasttime = time;
 +                                      turnangle = wishangle;
 +                              }
 +                              else // retain last state until strafe turning times out
 +                              {
 +                                      wishangle = turnangle;
 +                              }
 +
 +                              // calculate the maximum air strafe speed and acceleration
 +                              float strafity = 1 - (90 - fabs(wishangle)) / 45;
 +
 +                              if(PHYS_MAXAIRSTRAFESPEED(strafeplayer) != 0)
 +                                      maxspeed = min(maxspeed, GeomLerp(PHYS_MAXAIRSPEED(strafeplayer), strafity, PHYS_MAXAIRSTRAFESPEED(strafeplayer)));
 +
 +                              movespeed = min(movespeed, maxspeed);
 +
 +                              if(PHYS_AIRSTRAFEACCELERATE(strafeplayer) != 0)
 +                                      maxaccel = GeomLerp(PHYS_AIRACCELERATE(strafeplayer), strafity, PHYS_AIRSTRAFEACCELERATE(strafeplayer));
 +                      }
 +              }
 +
 +              maxaccel *= dt * movespeed;
 +              bestspeed = max(movespeed - maxaccel, 0); // target speed to gain maximum acceleration
 +
 +              float frictionspeed; // speed lost from friction
 +              float strafespeed; // speed minus friction
 +
 +              if((speed > 0) && onground)
 +              {
 +                      float strafefriction = onslick ? PHYS_FRICTION_SLICK(strafeplayer) : PHYS_FRICTION(strafeplayer);
 +
 +                      frictionspeed = speed * dt * strafefriction * max(PHYS_STOPSPEED(strafeplayer) / speed, 1);
 +                      strafespeed = max(speed - frictionspeed, 0);
 +              }
 +              else
 +              {
 +                      frictionspeed = 0;
 +                      strafespeed = speed;
 +              }
 +
 +              minspeed = autocvar_hud_panel_strafehud_switch_minspeed;
 +              if(minspeed < 0)
 +                      minspeed = bestspeed + frictionspeed;
 +
 +              // get current strafing angle ranging from -180° to +180°
 +              if(!autocvar__hud_configure)
 +              {
 +                      if(speed > 0)
 +                      {
 +                              // 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 since we want the interior angle
 +                              if(angle > 180)
 +                                      angle -= 360;
 +                              else if(angle < -180)
 +                                      angle += 360;
 +
 +                              // determine whether the player is strafing forwards or backwards
 +                              // if the player isn't strafe turning use forwards/backwards keys to determine direction
 +                              if(fabs(wishangle) != 90)
 +                              {
 +                                      if(keys_fwd == STRAFEHUD_KEYS_FORWARD)
 +                                              fwd = true;
 +                                      else if(keys_fwd == STRAFEHUD_KEYS_BACKWARD)
 +                                              fwd = false;
 +                                      else
 +                                              fwd = fabs(angle) <= 90;
 +                              }
 +                              // otherwise determine by examining the strafe angle
 +                              else
 +                              {
 +                                      if(wishangle < 0) // detect direction using wishangle since the direction is not yet set
 +                                              fwd = angle <= -wishangle;
 +                                      else
 +                                              fwd = angle >= -wishangle;
 +                              }
 +
 +                              // shift the strafe angle by 180° when strafing backwards
 +                              if(!fwd)
 +                              {
 +                                      if(angle < 0)
 +                                              angle += 180;
 +                                      else
 +                                              angle -= 180;
 +                              }
 +
 +                              // don't make the angle indicator switch side too much at ±180° if anti flicker is turned on
 +                              if(angle > (180 - antiflicker_angle) || angle < (-180 + antiflicker_angle))
 +                                      straight_overturn = true;
 +                      }
 +                      else
 +                      {
 +                              angle = 0;
 +                              fwd = true;
 +                      }
 +              }
 +              else // simulate turning for HUD setup
 +              {
 +                      const float demo_maxangle = 55; // maximum angle before changing direction
 +                      const float demo_turnspeed = 40; // turning speed in degrees per second
 +
 +                      static float demo_position = -37 / demo_maxangle; // current positioning value between -1 and +1
 +
 +                      if(autocvar__hud_panel_strafehud_demo)
 +                      {
 +                              float demo_dt = time - hud_lasttime;
 +                              float demo_step = (demo_turnspeed / demo_maxangle) * demo_dt;
 +                              demo_position = ((demo_position + demo_step) % 4 + 4) % 4;
 +                      }
 +
 +                      // triangle wave function
 +                      if(demo_position > 3)
 +                              angle = -1 + (demo_position - 3);
 +                      else if(demo_position > 1)
 +                              angle = +1 - (demo_position - 1);
 +                      else
 +                              angle = demo_position;
 +                      angle *= demo_maxangle;
 +
 +                      fwd = true;
 +                      wishangle = 45;
 +                      if(angle < 0)
 +                              wishangle *= -1;
 +              }
 +
 +              // invert the wish angle when strafing backwards
 +              if(!fwd)
 +                      wishangle *= -1;
 +
 +              // flip angles if v_flipped is enabled
 +              if(autocvar_v_flipped)
 +              {
 +                      angle *= -1;
 +                      wishangle *= -1;
 +              }
 +
 +              // determine whether the player is strafing left or right
 +              if(wishangle > 0)
 +              {
 +                      direction = STRAFEHUD_DIRECTION_RIGHT;
 +              }
 +              else if(wishangle < 0)
 +              {
 +                      direction = STRAFEHUD_DIRECTION_LEFT;
 +              }
 +              else
 +              {
 +                      if(angle > antiflicker_angle && angle < (180 - antiflicker_angle))
 +                              direction = STRAFEHUD_DIRECTION_RIGHT;
 +                      else if(angle < -antiflicker_angle && angle > (-180 + antiflicker_angle))
 +                              direction = STRAFEHUD_DIRECTION_LEFT;
 +                      else
 +                              direction = STRAFEHUD_DIRECTION_NONE;
 +              }
 +
 +              // best angle to strafe at
 +              // in case of ground friction we may decelerate if the acceleration is smaller than the speed loss from friction
 +              real_bestangle = bestangle = (strafespeed > bestspeed ? acos(bestspeed / strafespeed) * RAD2DEG : 0);
 +              real_prebestangle = prebestangle = (strafespeed > movespeed ? acos(movespeed / strafespeed) * RAD2DEG : 0);
 +              if(direction == STRAFEHUD_DIRECTION_LEFT) // the angle becomes negative in case we strafe left
 +              {
 +                      bestangle *= -1;
 +                      prebestangle *= -1;
 +              }
 +              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 = autocvar_hud_panel_strafehud_angle_width;
 +              currentangle_size.y = autocvar_hud_panel_strafehud_angle_height;
 +              currentangle_size.z = 0;
 +              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 == STRAFEHUD_MODE_VIEW_CENTERED)
 +                      currentangle_offset = angle / hudangle * panel_size.x;
 +              else
 +
 +                      currentangle_offset = bound(-hudangle / 2, angle, hudangle / 2) / hudangle * panel_size.x + panel_size.x / 2;
 +
 +              // 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 = 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 == STRAFEHUD_DIRECTION_LEFT) || (angle < -wishangle && direction == STRAFEHUD_DIRECTION_RIGHT))
 +              {
 +                      odd_angles = true;
 +                      odd_bestangle_offset = odd_bestangle / hudangle * panel_size.x + panel_size.x / 2;
 +                      switch_odd_bestangle_offset = (odd_bestangle + bestangle * 2) / hudangle * panel_size.x + panel_size.x / 2;
 +              }
 +              // direction indicator
 +              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_vertical.z = 0;
 +              direction_size_horizontal.x = panel_size.x * min(autocvar_hud_panel_strafehud_direction_length, .5);
 +              direction_size_horizontal.y = direction_size_vertical.x;
 +              direction_size_horizontal.z = 0;
 +
 +              // the neutral zone fills the whole strafe bar
 +              if(immobile)
 +              {
 +                      // draw neutral zone
 +                      if(panel_size.x > 0 && panel_size.y > 0 && autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha > 0)
 +                      {
 +                              switch(autocvar_hud_panel_strafehud_style)
 +                              {
 +                                      default:
 +                                      case STRAFEHUD_STYLE_DRAWFILL:
 +                                              drawfill(
 +                                                      panel_pos, panel_size,
 +                                                      autocvar_hud_panel_strafehud_bar_neutral_color,
 +                                                      autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha,
 +                                                      DRAWFLAG_NORMAL);
 +                                              break;
 +
 +                                      case STRAFEHUD_STYLE_PROGRESSBAR:
 +                                              HUD_Panel_DrawProgressBar(
 +                                                      panel_pos, panel_size, "progressbar", 1, 0, 0,
 +                                                      autocvar_hud_panel_strafehud_bar_neutral_color,
 +                                                      autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha,
 +                                                      DRAWFLAG_NORMAL);
 +                              }
 +                      }
 +              }
 +              else
 +              {
 +                      // calculate various zones of the strafe-o-meter
 +                      if(autocvar_hud_panel_strafehud_bar_preaccel)
 +                              preaccelzone_width = (fabs(bestangle - prebestangle)) / hudangle * panel_size.x;
 +                      else
 +                              preaccelzone_width = 0;
 +                      accelzone_width = (90 - fabs(bestangle + wishangle)) / hudangle * panel_size.x;
 +                      overturn_width = 180 / hudangle * panel_size.x;
 +                      neutral_width = 360 / hudangle * panel_size.x - accelzone_width * 2 - preaccelzone_width * 2 - overturn_width;
 +
 +                      {
 +                              float current_offset = 0;
 +                              preaccelzone_right_offset = current_offset;
 +                              current_offset += preaccelzone_width;
 +
 +                              accelzone_right_offset = current_offset;
 +                              current_offset += accelzone_width;
 +
 +                              overturn_offset = current_offset;
 +                              current_offset += overturn_width;
 +
 +                              accelzone_left_offset = current_offset;
 +                              current_offset += accelzone_width;
 +
 +                              preaccelzone_left_offset = current_offset;
 +                              current_offset += preaccelzone_width;
 +
 +                              // the wrapping code may struggle if we always append it on the right side
 +                              neutral_offset = direction == STRAFEHUD_DIRECTION_LEFT ? current_offset : -neutral_width;
 +                      }
 +
 +                      // shift hud if operating in view angle centered mode
 +                      if(mode == STRAFEHUD_MODE_VIEW_CENTERED)
 +                      {
 +                              shift_offset = -currentangle_offset;
 +                              bestangle_offset += shift_offset;
 +                              switch_bestangle_offset += shift_offset;
 +                              odd_bestangle_offset += shift_offset;
 +                              switch_odd_bestangle_offset += shift_offset;
 +                      }
 +                      if(direction == STRAFEHUD_DIRECTION_LEFT)
 +                              shift_offset += -360 / hudangle * panel_size.x;
 +
 +                      // calculate how far off-center the strafe zones currently are
 +                      shift_offset += (panel_size.x + neutral_width) / 2 - wishangle / hudangle * panel_size.x;
 +
 +                      // shift strafe zones into correct place
 +                      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, hidden_width,
 +                              autocvar_hud_panel_strafehud_bar_accel_color,
 +                              autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha,
 +                              autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_LEFT);
 +
 +                      if(autocvar_hud_panel_strafehud_bar_preaccel)
 +                              HUD_Panel_DrawStrafeHUD(
 +                                      preaccelzone_left_offset, preaccelzone_width, hidden_width,
 +                                      autocvar_hud_panel_strafehud_bar_accel_color,
 +                                      autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha,
 +                                      autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_RIGHT);
 +
 +                      // draw right acceleration zone
 +                      HUD_Panel_DrawStrafeHUD(
 +                              accelzone_right_offset, accelzone_width, hidden_width,
 +                              autocvar_hud_panel_strafehud_bar_accel_color,
 +                              autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha,
 +                              autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_RIGHT);
 +
 +                      if(autocvar_hud_panel_strafehud_bar_preaccel)
 +                              HUD_Panel_DrawStrafeHUD(
 +                                      preaccelzone_right_offset, preaccelzone_width, hidden_width,
 +                                      autocvar_hud_panel_strafehud_bar_accel_color,
 +                                      autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha,
 +                                      autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_LEFT);
 +
 +                      // draw overturn zone
 +                      //   this is technically incorrect
 +                      //   acceleration decreases at 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, hidden_width,
 +                              autocvar_hud_panel_strafehud_bar_overturn_color,
 +                              autocvar_hud_panel_strafehud_bar_overturn_alpha * panel_fg_alpha,
 +                              autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_BOTH);
 +
 +                      // draw neutral zone
 +                      HUD_Panel_DrawStrafeHUD(
 +                              neutral_offset, neutral_width, hidden_width,
 +                              autocvar_hud_panel_strafehud_bar_neutral_color,
 +                              autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha,
 +                              autocvar_hud_panel_strafehud_style, STRAFEHUD_GRADIENT_NONE);
 +
 +                      // only draw indicators if minspeed is reached
 +                      if(autocvar_hud_panel_strafehud_switch && speed >= minspeed && bestangle_width > 0 && autocvar_hud_panel_strafehud_switch_alpha > 0)
 +                      {
 +                              // 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 == STRAFEHUD_DIRECTION_LEFT)
 +                              {
 +                                      if(!odd_angles)
 +                                              offset -= bestangle_width;
 +                                      else
 +                                              switch_offset -= bestangle_width;
 +                              }
 +                              else
 +                              {
 +                                      if(!odd_angles)
 +                                              switch_offset -= bestangle_width;
 +                                      else
 +                                              offset -= bestangle_width;
 +                              }
 +
 +                              HUD_Panel_DrawStrafeHUD(
 +                                      switch_offset, bestangle_width, hidden_width,
 +                                      autocvar_hud_panel_strafehud_switch_color,
 +                                      autocvar_hud_panel_strafehud_switch_alpha * panel_fg_alpha,
 +                                      STRAFEHUD_STYLE_DRAWFILL, STRAFEHUD_GRADIENT_NONE);
 +
 +                              if(direction == STRAFEHUD_DIRECTION_NONE)
 +                                      HUD_Panel_DrawStrafeHUD(
 +                                              offset, bestangle_width, hidden_width,
 +                                              autocvar_hud_panel_strafehud_switch_color,
 +                                              autocvar_hud_panel_strafehud_switch_alpha * panel_fg_alpha,
 +                                              STRAFEHUD_STYLE_DRAWFILL, STRAFEHUD_GRADIENT_NONE);
 +                      }
 +              }
 +
 +              // 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 &&
 +                 autocvar_hud_panel_strafehud_slickdetector_range > 0 &&
 +                 autocvar_hud_panel_strafehud_slickdetector_alpha > 0 &&
 +                 slickdetector_height > 0 &&
 +                 panel_size.x > 0)
 +              {
 +                      float slicksteps = max(autocvar_hud_panel_strafehud_slickdetector_granularity, 0);
 +                      bool slickdetected = false;
 +
 +                      if(!autocvar_hud_panel_strafehud_uncapped)
 +                              slicksteps = min(slicksteps, 4);
 +                      slicksteps = 90 / 2 ** slicksteps;
 +
 +                      slickdetected = real_onslick; // don't need to traceline if already touching slick
 +
 +                      // traceline into every direction
 +                      trace_dphitq3surfaceflags = 0;
 +                      vector traceorigin = strafeplayer.origin + eZ * strafeplayer.mins.z;
 +                      for(float i = 0; i < 90 && !slickdetected; i += slicksteps)
 +                      {
 +                              vector slickoffset;
 +                              float slickrotate;
 +                              slickoffset.z = -cos(i * DEG2RAD) * autocvar_hud_panel_strafehud_slickdetector_range;
 +                              slickrotate = sin(i * DEG2RAD) * autocvar_hud_panel_strafehud_slickdetector_range;
 +
 +                              for(float j = 0; j < 360 && !slickdetected; j += slicksteps)
 +                              {
 +                                      slickoffset.x = sin(j * DEG2RAD) * slickrotate;
 +                                      slickoffset.y = cos(j * DEG2RAD) * slickrotate;
 +
 +                                      traceline(traceorigin, traceorigin + slickoffset, MOVE_NOMONSTERS, strafeplayer);
 +                                      if((PHYS_FRICTION(strafeplayer) == 0 && trace_fraction < 1) || trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)
 +                                              slickdetected = true;
 +                                      if(i == 0)
 +                                              break;
 +                              }
 +                      }
 +
 +                      // if a traceline hit a slick surface
 +                      if(slickdetected)
 +                      {
 +                              vector slickdetector_size = panel_size;
 +                              slickdetector_size.y = slickdetector_height;
 +
 +                              // top horizontal line
 +                              drawfill(
 +                                      panel_pos - eY * slickdetector_size.y, slickdetector_size,
 +                                      autocvar_hud_panel_strafehud_slickdetector_color,
 +                                      autocvar_hud_panel_strafehud_slickdetector_alpha * panel_fg_alpha,
 +                                      DRAWFLAG_NORMAL);
 +
 +                              // bottom horizontal line
 +                              drawfill(
 +                                      panel_pos + eY * panel_size.y,
 +                                      slickdetector_size, autocvar_hud_panel_strafehud_slickdetector_color,
 +                                      autocvar_hud_panel_strafehud_slickdetector_alpha * panel_fg_alpha,
 +                                      DRAWFLAG_NORMAL);
 +                      }
 +
 +                      text_offset_top = text_offset_bottom = slickdetector_height;
 +              }
 +
 +              if(autocvar_hud_panel_strafehud_direction &&
 +                 direction != STRAFEHUD_DIRECTION_NONE &&
 +                 direction_size_vertical.x > 0 &&
 +                 autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha > 0)
 +              {
 +                      bool indicator_direction = direction == STRAFEHUD_DIRECTION_LEFT;
 +                      // 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)
 +                              indicator_direction = !indicator_direction;
 +
 +                      // 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);
 +              }
 +
 +              // draw the actual strafe angle
 +              if(!immobile)
 +              {
 +                      float moveangle = fabs(angle + wishangle);
 +                      float strafe_ratio = 0;
 +
 +                      // player is overturning
 +                      if(moveangle >= 90)
 +                      {
 +                              currentangle_color = autocvar_hud_panel_strafehud_angle_overturn_color;
 +                              strafe_ratio = (moveangle - 90) / 90;
 +                              if(strafe_ratio > 1) strafe_ratio = 2 - strafe_ratio;
 +                              strafe_ratio *= -1;
 +                      }
 +                      // player gains speed by strafing
 +                      else if(moveangle >= real_bestangle)
 +                      {
 +                              currentangle_color = autocvar_hud_panel_strafehud_angle_accel_color;
 +                              strafe_ratio = (90 - moveangle) / (90 - real_bestangle);
 +                      }
 +                      else if(moveangle >= real_prebestangle)
 +                      {
 +                              if(autocvar_hud_panel_strafehud_bar_preaccel)
 +                                      currentangle_color = autocvar_hud_panel_strafehud_angle_accel_color;
 +                              strafe_ratio = (moveangle - real_prebestangle) / (real_bestangle - real_prebestangle);
 +                      }
 +
 +                      if(autocvar_hud_panel_strafehud_style == STRAFEHUD_STYLE_GRADIENT)
 +                              currentangle_color = StrafeHUD_mixColors(autocvar_hud_panel_strafehud_angle_neutral_color, currentangle_color, fabs(strafe_ratio));
 +              }
 +
 +              if(mode == STRAFEHUD_MODE_VIEW_CENTERED || straight_overturn)
 +                      currentangle_offset = panel_size.x / 2;
 +
 +              float angleheight_offset = currentangle_size.y;
 +              float ghost_offset = 0;
 +              if(autocvar_hud_panel_strafehud_bestangle && direction != STRAFEHUD_DIRECTION_NONE)
 +                      ghost_offset = bound(0, (odd_angles ? odd_bestangle_offset : bestangle_offset), panel_size.x);
 +
 +              switch(autocvar_hud_panel_strafehud_angle_style)
 +              {
 +                      case STRAFEHUD_INDICATOR_SOLID:
 +                              if(currentangle_size.x > 0 && currentangle_size.y > 0)
 +                              {
 +                                      if(autocvar_hud_panel_strafehud_bestangle && direction != STRAFEHUD_DIRECTION_NONE)
 +                                              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 STRAFEHUD_INDICATOR_DASHED:
 +                              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 != STRAFEHUD_DIRECTION_NONE)
 +                                                      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 STRAFEHUD_INDICATOR_NONE:
 +                      default:
 +                              // don't offset text and arrows if the angle indicator line isn't drawn
 +                              angleheight_offset = panel_size.y;
 +                              currentangle_size = '0 0 0';
 +              }
 +
 +              float angle_offset_top = 0, angle_offset_bottom = 0;
 +
 +              // offset text if any angle indicator is drawn
 +              if((autocvar_hud_panel_strafehud_angle_alpha > 0) ||
 +                 (autocvar_hud_panel_strafehud_bestangle && autocvar_hud_panel_strafehud_bestangle_alpha > 0))
 +              {
 +                      // offset text by amount the angle indicator extrudes from the strafehud bar
 +                      angle_offset_top = angle_offset_bottom = (angleheight_offset - panel_size.y) / 2;
 +              }
 +
 +              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 != STRAFEHUD_DIRECTION_NONE)
 +                                              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, currentangle_size.x);
 +                                      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, currentangle_size.x);
 +
 +                                      angle_offset_top += arrow_size; // further offset the top text offset if the top arrow is drawn
 +                              }
 +                              if(autocvar_hud_panel_strafehud_angle_arrow >= 2)
 +                              {
 +                                      if(autocvar_hud_panel_strafehud_bestangle && direction != STRAFEHUD_DIRECTION_NONE)
 +                                              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, currentangle_size.x);
 +                                      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, currentangle_size.x);
 +
 +                                      angle_offset_bottom += arrow_size; // further offset the bottom text offset if the bottom arrow is drawn
 +                              }
 +                      }
 +              }
 +
 +              // make sure text doesn't draw inside the strafehud bar
 +              text_offset_top = max(angle_offset_top, text_offset_top);
 +              text_offset_bottom = max(angle_offset_bottom, text_offset_bottom);
 +
 +              draw_beginBoldFont();
 +
 +              // show speed when crossing the start trigger
 +              {
 +                      static float startspeed = 0, starttime = 0; // displayed value and timestamp for fade out
 +
 +                      // check if the start trigger was hit (will also trigger if the finish trigger was hit if those have the same ID)
 +                      if((race_nextcheckpoint == 1) || (race_checkpoint == 254 && race_nextcheckpoint == 255))
 +                      {
 +                              if((race_checkpointtime > 0) && (starttime != race_checkpointtime))
 +                              {
 +                                      starttime = race_checkpointtime;
 +                                      startspeed = speed;
 +                              }
 +                      }
 +
 +                      if(autocvar_hud_panel_strafehud_startspeed)
 +                      {
 +                              float startspeed_height = autocvar_hud_panel_strafehud_startspeed_size * panel_size.y;
 +                              string startspeed_text = ftos_decimals(startspeed * speed_conversion_factor, 2);
 +                              if(autocvar_hud_panel_strafehud_unit_show)
-                                       startspeed_text = strcat(startspeed_text, GetSpeedUnit(autocvar_hud_panel_strafehud_unit));
++                                      startspeed_text = strcat(startspeed_text, GetSpeedUnit(autocvar_hud_speed_unit));
 +
 +                              bool was_drawn = StrafeHUD_drawTextIndicator(
 +                                      startspeed_text, startspeed_height,
 +                                      autocvar_hud_panel_strafehud_startspeed_color,
 +                                      autocvar_hud_panel_strafehud_startspeed_fade,
 +                                      starttime, text_offset_bottom, STRAFEHUD_TEXT_BOTTOM);
 +
 +                              if(was_drawn)
 +                                      text_offset_bottom += startspeed_height;
 +                      }
 +              }
 +
 +              // 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 than physics, doesn't run when hud isn't drawn, rounding errors)
 +              {
 +                      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
 +                      if((strafeplayer.velocity.z <= 0) || real_onground || swimming || IS_DEAD(strafeplayer) || !IS_PLAYER(strafeplayer))
 +                      {
 +                              height_min = height_max = strafeplayer.origin.z;
 +                      }
 +                      else if(strafeplayer.origin.z > height_max)
 +                      {
 +                              height_max = strafeplayer.origin.z;
 +                              float jumpheight_new = height_max - height_min;
 +
 +                              if((jumpheight_new * length_conversion_factor) > max(autocvar_hud_panel_strafehud_jumpheight_min, 0))
 +                              {
 +                                      jumpheight = jumpheight_new;
 +                                      jumptime = time;
 +                              }
 +                      }
 +
 +                      if(autocvar_hud_panel_strafehud_jumpheight)
 +                      {
 +                              float jumpheight_height = autocvar_hud_panel_strafehud_jumpheight_size * panel_size.y;
 +                              string jumpheight_text = ftos_decimals(jumpheight * length_conversion_factor, length_decimals);
 +                              if(autocvar_hud_panel_strafehud_unit_show)
-                                       jumpheight_text = strcat(jumpheight_text, GetLengthUnit(autocvar_hud_panel_strafehud_unit));
++                                      jumpheight_text = strcat(jumpheight_text, GetLengthUnit(autocvar_hud_speed_unit));
 +
 +                              bool was_drawn = StrafeHUD_drawTextIndicator(
 +                                      jumpheight_text, jumpheight_height,
 +                                      autocvar_hud_panel_strafehud_jumpheight_color,
 +                                      autocvar_hud_panel_strafehud_jumpheight_fade,
 +                                      jumptime, text_offset_top, STRAFEHUD_TEXT_TOP);
 +
 +                              if(was_drawn)
 +                                      text_offset_top += jumpheight_height;
 +                      }
 +              }
 +
 +              draw_endBoldFont();
 +      }
 +      hud_lasttime = time;
  }
  
  // functions to make hud elements align perfectly in the hud area
index e726e98005b540919822d54af2161ff0ed1f6a00,64a78c981466ec9a8eda3a631c63f2561694b119..4f59ce3c4a2997447800dbbe80a34cbd14dad003
@@@ -5,23 -5,18 +5,22 @@@ int autocvar_hud_panel_strafehud = 3
  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;
 +float autocvar_hud_panel_strafehud_range = 90;
 +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;
 +bool autocvar_hud_panel_strafehud_bar_preaccel = true;
  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_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';