]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch master into Juhu/strafehud-fixes
authorJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Sun, 18 Jul 2021 17:41:33 +0000 (19:41 +0200)
committerJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Sun, 18 Jul 2021 17:41:33 +0000 (19:41 +0200)
Fix merge conflict caused by GetSpeedUnit call optimization in master

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

diff --combined _hud_common.cfg
index ca6e7fb025a8d87c11966d0298a1a9e27a4bfcd9,2d5aa2cfa1a374d2fbc80ab89328177984611af9..c6ccb2b82a0f11d3a40228433bbd6e6522e579f5
@@@ -120,6 -120,7 +120,7 @@@ seta hud_panel_scoreboard_maxheight 0.
  seta hud_panel_scoreboard_others_showscore 1 "show scores of players listed in the last row when the scoreboard reaches the max height"
  seta hud_panel_scoreboard_spectators_showping 1 "show ping of spectators"
  seta hud_panel_scoreboard_spectators_aligned 0 "align spectators in columns"
+ seta hud_panel_scoreboard_spectators_position 1 "spectator list position (0 = before accuracy and itemstats, 1 = before rankings, 2 = after rankings, 3 = after map stats)"
  seta hud_panel_scoreboard_minwidth 0.6 "minimum width of the scoreboard"
  seta hud_panel_scoreboard_team_size_position 0 "where to show the team size (0 = do not show, 1 = left of scoreboard, 2 = right of scoreboard), will move team scores to the other side if necessary"
  seta hud_panel_scoreboard_playerid 0 "show player id (server entity number) next to player's name"
@@@ -129,52 -130,45 +130,53 @@@ seta hud_panel_scoreboard_playerid_suff
  seta hud_panel_scoreboard_accuracy_showdelay 2 "how long to delay displaying accuracy below the scoreboard if it's too far down"
  seta hud_panel_scoreboard_accuracy_showdelay_minpos 0.75 "delay displaying the accuracy panel only if its position is lower than this percentage of the screen height from the top"
  
- seta hud_panel_scoreboard_itemstats_filter 1 "filter out less interesting items (ammo and smaller health/armor)"
+ seta hud_panel_scoreboard_itemstats_filter 1 "filter out less interesting items (according to hud_panel_scoreboard_itemstats_filter_mask)"
+ seta hud_panel_scoreboard_itemstats_filter_mask 12 "[0-1][0-4]: the tens digit filters out all ammo items if set to 1, the units digit filters out health/armor items (1 small, 2 medium too, 3 big too, 4 mega too)"
  seta hud_panel_scoreboard_itemstats_showdelay 2.2 "how long to delay displaying item stats below the scoreboard if it's too far down"
  seta hud_panel_scoreboard_itemstats_showdelay_minpos 0.75 "delay displaying the item stats panel only if its position is lower than this percentage of the screen height from the top"
  
  seta _hud_panel_strafehud_demo "0" "strafehud changes angle during configure"
  seta hud_panel_strafehud_mode "0" "strafehud mode which controls whether the strafehud is centered at \"0\" = view angle, \"1\" = velocity angle"
 -seta hud_panel_strafehud_range "0" "the angle range up to 360 degrees displayed on the strafehud, \"0\" = dynamic (chooses the minimum range required to still see the whole area needed for accelerating at once)"
 -seta hud_panel_strafehud_style "1" "\"0\" = no styling, \"1\" = progress bar style for the strafe bar, \"2\" = gradient for the strafe bar"
 +seta hud_panel_strafehud_range "0" "the angle range up to 360 degrees displayed on the strafehud, \"0\" = dynamic (chooses the minimum range required to still see the whole area needed for accelerating)"
 +seta hud_panel_strafehud_style "2" "\"0\" = no styling, \"1\" = progress bar style for the strafe bar, \"2\" = gradient for the strafe bar"
  seta hud_panel_strafehud_unit "1" "speed unit (1 = qu/s, 2 = m/s, 3 = km/h, 4 = mph, 5 = knots), length unit (1 = qu, 2 = m, 3 = km, 4 = mi, 5 = nmi)"
  seta hud_panel_strafehud_unit_show "1" "show units"
 +seta hud_panel_strafehud_uncapped "0" "set to \"1\" to remove some safety restrictions, useful to set thinner indicator lines down to 1px or for trying out higher values for some performance degrading operations (warning: elements may turn invisible if too thin, other configurations may crash your game or look horribly ugly)"
  seta hud_panel_strafehud_bar_neutral_color "1 1 1" "color of the strafe meter neutral zone"
 -seta hud_panel_strafehud_bar_neutral_alpha "0.3" "opacity of the strafe meter neutral zone"
 +seta hud_panel_strafehud_bar_neutral_alpha "0.1" "opacity of the strafe meter neutral zone"
  seta hud_panel_strafehud_bar_accel_color "0 1 0" "color of the strafe meter acceleration zone"
 -seta hud_panel_strafehud_bar_accel_alpha "0.3" "opacity of the strafe meter acceleration zone"
 +seta hud_panel_strafehud_bar_accel_alpha "0.5" "opacity of the strafe meter acceleration zone"
 +seta hud_panel_strafehud_bar_preaccel_color "1 0.75 0" "color of the strafe meter zone before full acceleration can be achieved"
 +seta hud_panel_strafehud_bar_preaccel_alpha "0.5" "opacity of the strafe meter zone before full acceleration can be achieved"
  seta hud_panel_strafehud_bar_overturn_color "1 0 1" "color of the strafe meter overturn zone"
 -seta hud_panel_strafehud_bar_overturn_alpha "0.3" "opacity of the strafe meter overturn zone"
 +seta hud_panel_strafehud_bar_overturn_alpha "0.5" "opacity of the strafe meter overturn zone"
 +seta hud_panel_strafehud_angle_style "0" "set the angle indicator style: 0 = none, 1 = solid line, 2 = dashed line"
 +seta hud_panel_strafehud_angle_dashes "4" "determines the amount of dashes if the angle indicator uses a dashed line"
  seta hud_panel_strafehud_angle_alpha "0.8" "opacity of the indicator showing the player's current angle"
 -seta hud_panel_strafehud_angle_height "1.5" "height of the indicator showing the player's current angle (relative to the panel height)"
 -seta hud_panel_strafehud_angle_width "0.005" "width of the indicator showing the player's current angle (relative to the panel width)"
 -seta hud_panel_strafehud_angle_neutral_color "1 1 0" "color of the indicator showing the player's current angle if the player's angle is within the neutral zone"
 -seta hud_panel_strafehud_angle_accel_color "0 1 1" "color of the indicator showing the player's current angle if the player's angle is within the acceleration zone"
 -seta hud_panel_strafehud_angle_overturn_color "1 0 1" "color of the indicator showing the player's current angle if the player's angle is within the overturn zone"
 -seta hud_panel_strafehud_switch_minspeed "-1" "minimum speed in qu/s at which switch indicators which are used to aid changing strafe direction will be shown (uses physics maxspeed + antiflicker speed if negative)"
 -seta hud_panel_strafehud_switch_active_color "0 1 0" "color of the switch indicator on the current side"
 -seta hud_panel_strafehud_switch_active_alpha "1" "opacity of the switch indicator on the current side"
 -seta hud_panel_strafehud_switch_inactive_color "1 1 0" "color of the switch indicator on the opposite side"
 -seta hud_panel_strafehud_switch_inactive_alpha "1" "opacity of the switch indicator on the opposite side"
 -seta hud_panel_strafehud_switch_width "0.0075" "width of the strafe angle indicators (relative to the strafe bar width)"
 +seta hud_panel_strafehud_angle_height "1" "height of the indicator showing the player's current angle (relative to the panel height)"
 +seta hud_panel_strafehud_angle_width "0.001" "width of the indicator showing the player's current angle (relative to the panel width)"
 +seta hud_panel_strafehud_angle_neutral_color "1 1 0" "color of the indicator showing the player's current angle if it is within the neutral zone"
 +seta hud_panel_strafehud_angle_accel_color "0 1 1" "color of the indicator showing the player's current angle if it is within the acceleration zone"
 +seta hud_panel_strafehud_angle_overturn_color "1 0 1" "color of the indicator showing the player's current angle if it is within the overturn zone"
 +seta hud_panel_strafehud_angle_arrow "1" "set the angle indicator's arrow style: 0 = none, 1 = top, 2 = bottom, 3 = both"
 +seta hud_panel_strafehud_angle_arrow_size "0.5" "size of the arrow (relative to the panel height)"
 +seta hud_panel_strafehud_bestangle "0" "set to \"1\" to enable a ghost angle indicator showing the best angle to gain maximum acceleration"
 +seta hud_panel_strafehud_bestangle_color "1 1 1" "color of the indicator showing the best angle to gain maximum acceleration"
 +seta hud_panel_strafehud_bestangle_alpha "0.5" "opacity of the indicator showing the best angle to gain maximum acceleration"
 +seta hud_panel_strafehud_switch_minspeed "-1" "minimum speed in qu/s at which switch indicator(s) which are used to aid changing strafe direction will be shown (set to -1 for dynamic minspeed)"
 +seta hud_panel_strafehud_switch_color "1 1 0" "color of the switch indicator"
 +seta hud_panel_strafehud_switch_alpha "1" "opacity of the switch indicator"
 +seta hud_panel_strafehud_switch_width "0.003" "width of the strafe angle indicator(s) (relative to the strafe bar width)"
  seta hud_panel_strafehud_direction_color "0 0.5 1" "color of the direction caps which indicate the direction the player is currently strafing towards"
 -seta hud_panel_strafehud_direction_alpha "1" "opacity of the direction caps which indicate the direction the player is currently strafing towards"
 +seta hud_panel_strafehud_direction_alpha "0" "opacity of the direction caps which indicate the direction the player is currently strafing towards"
  seta hud_panel_strafehud_direction_width "0.25" "stroke width of the direction caps which indicate the direction the player is currently strafing towards (relative to the panel height)"
  seta hud_panel_strafehud_direction_length "0.02" "length of the horizontal component of the direction caps which indicate the direction the player is currently strafing towards (relative to the panel width)"
 -seta hud_panel_strafehud_slickdetector_range "0" "range of the slick detector in qu, \"0\" to disable"
 -seta hud_panel_strafehud_slickdetector_granularity "2" "value from 0 to 4 which defines how exact the search for slick should be, higher values may yield better results but require more computation"
 +seta hud_panel_strafehud_slickdetector_range "200" "range of the slick detector in qu, \"0\" to disable"
 +seta hud_panel_strafehud_slickdetector_granularity "4" "value from 0 to 4 which defines how exact the search for slick should be, higher values may yield better results but require more computation"
  seta hud_panel_strafehud_slickdetector_color "0 1 1" "color of the slick detector indicator"
  seta hud_panel_strafehud_slickdetector_alpha "0.5" "opacity of the slick detector indicator"
  seta hud_panel_strafehud_slickdetector_height "0.125" "height of the slick detector indicator (relative to the panel height)"
 -seta hud_panel_strafehud_startspeed_fade "0" "fade time (in seconds) of the start speed text or \"0\" to disable"
 +seta hud_panel_strafehud_startspeed_fade "4" "fade time (in seconds) of the start speed text or \"0\" to disable"
  seta hud_panel_strafehud_startspeed_color "1 0.75 0" "color of the start speed text"
  seta hud_panel_strafehud_startspeed_size "1.5" "size of the start speed text (relative to the panel height)"
  seta hud_panel_strafehud_jumpheight_fade "0" "fade time (in seconds) of the jump height text or \"0\" to disable"
@@@ -184,6 -178,7 +186,6 @@@ seta hud_panel_strafehud_jumpheight_siz
  seta hud_panel_strafehud_timeout_air "0.1" "time (in seconds) after take off before changing to air strafe physics when not jumping (visually more consistent hud while on slick downwards ramps)"
  seta hud_panel_strafehud_timeout_ground "0.03333333" "time (in seconds) after landing before changing to non-air strafe physics (visually more consistent hud while strafe turning when touching the floor after every hop)"
  seta hud_panel_strafehud_timeout_turn "0.1" "time (in seconds) after releasing the strafe keys before changing mode (visually more consistent hud while switching between left/right strafe turning)"
 -seta hud_panel_strafehud_timeout_direction "0.5" "time (in seconds) it takes until direction changes (forward or backward movement) are applied (set to zero if you intend to sideways strafe)"
  seta hud_panel_strafehud_antiflicker_angle "0.01" "how many degrees from 0° to 180° the hud ignores if it could cause visual disturbances otherwise (and to counter rounding errors)"
  seta hud_panel_strafehud_antiflicker_speed "0.0001" "how many qu/s the hud ignores if it could cause visual disturbances otherwise (and to counter rounding errors)"
  
index 071e8ebfb4e670dda546eb6b16aa1c3489c98edb,2f0df08213ec3c599888571f4063b77fe99185a8..ba1acb5e7c641be1f7ee474976bd075a97e3d5dd
@@@ -31,9 -31,10 +31,9 @@@ bool state_strafekeys = false
  float state_strafekeys_time = 0;
  bool turn = false;
  float turnangle;
 +float turnspeed;
 +float turnaccel;
  bool fwd = true;
 -bool state_fwd = true;
 -bool state_fwd_prev = true;
 -float state_fwd_time = 0;
  float starttime = 0;
  float startspeed = -1;
  float jumptime = 0;
@@@ -107,15 -108,12 +107,15 @@@ void HUD_StrafeHUD(
          bool   swimming                      = strafeplayer.waterlevel >= WATERLEVEL_SWIMMING;
          bool   spectating                    = entcs_GetSpecState(strafeplayer.sv_entnum) == ENTCS_SPEC_PURE;
          float  speed                         = !autocvar__hud_configure ? vlen(vec2(csqcplayer.velocity)) : 1337; // use local csqcmodel entity for this even when spectating, flickers too much otherwise
 -        float  maxspeed_crouch_mod           = IS_DUCKED(strafeplayer) && !swimming ? .5 : 1;
 -        float  maxspeed_water_mod            = swimming ? .7 : 1; // very simplified water physics, the hud will not work well (and is not supposed to) while swimming
 +        float  crouch_mod                    = IS_DUCKED(csqcplayer) && !swimming ? .5 : 1;
 +        float  water_mod                     = swimming ? .7 : 1; // very simplified water physics, the hud will not work well (and is not supposed to) while swimming
          float  maxspeed_phys                 = onground ? PHYS_MAXSPEED(strafeplayer) : PHYS_MAXAIRSPEED(strafeplayer);
 -        float  maxspeed                      = !autocvar__hud_configure ? maxspeed_phys * maxspeed_crouch_mod * maxspeed_water_mod : 320;
 -        float  vel_angle                     = vectoangles(strafeplayer.velocity).y;
 -        float  view_angle                    = PHYS_INPUT_ANGLES(strafeplayer).y + 180;
 +        float  maxspeed                      = !autocvar__hud_configure ? maxspeed_phys * crouch_mod * water_mod : 320;
 +        float  movespeed;
 +        float  maxaccel_phys                 = onground ? PHYS_ACCELERATE(strafeplayer) : PHYS_AIRACCELERATE(strafeplayer);
 +        float  maxaccel                      = !autocvar__hud_configure ? maxaccel_phys * crouch_mod * water_mod : 1;
 +        float  vel_angle                     = vectoangles(strafeplayer.velocity).y - (vectoangles(strafeplayer.velocity).y > 180 ? 360 : 0); // change the range from 0° - 360° to -180° - 180° to match how view_angle represents angles
 +        float  view_angle                    = PHYS_INPUT_ANGLES(strafeplayer).y;
          float  angle;
          vector movement                      = PHYS_INPUT_MOVEVALUES(strafeplayer);
          int    keys                          = STAT(PRESSED_KEYS);
          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_panel_strafehud_unit);
          float  length_conversion_factor      = GetLengthUnitFactor(autocvar_hud_panel_strafehud_unit);
-         string speed_unit                    = GetSpeedUnit(autocvar_hud_panel_strafehud_unit);
-         string length_unit                   = GetLengthUnit(autocvar_hud_panel_strafehud_unit);
          int    length_decimals               = autocvar_hud_panel_strafehud_unit >= 3 && autocvar_hud_panel_strafehud_unit <= 5 ? 6 : 2; // use more decimals when displaying km or miles
          float  antiflicker_angle             = bound(0, autocvar_hud_panel_strafehud_antiflicker_angle, 180);
          float  antiflicker_speed             = max(0, autocvar_hud_panel_strafehud_antiflicker_speed);
          float  currentangle_offset;
          vector currentangle_size             = '0 0 0';
          float  bestangle;
 +        float  prebestangle;
          float  odd_bestangle;
          bool   bestangle_anywhere            = false;
          float  bestangle_offset;
          float  accelzone_left_offset;
          float  accelzone_right_offset;
          float  accelzone_width;
 +        float  preaccelzone_left_offset;
 +        float  preaccelzone_right_offset;
 +        float  preaccelzone_width;
          float  overturn_offset;
          float  overturn_width;
          float  slickdetector_height;
          vector direction_size_vertical       = '0 0 0';
          vector direction_size_horizontal     = '0 0 0';
          float  range_minangle;
 +        float  arrow_size = max(panel_size.y * min(autocvar_hud_panel_strafehud_angle_arrow_size, 10), 0); // there's only one size cvar for the arrows, they will always have a 45° angle to ensure proper rendering without antialiasing
 +
 +        if(!autocvar_hud_panel_strafehud_uncapped)
 +            arrow_size = max(arrow_size, 1);
  
          // determine whether the player is pressing forwards or backwards keys
          if(islocal) // if entity is local player
              }
          }
  
 -        strafekeys = fabs(wishangle) == 90;
 +        strafekeys = fabs(wishangle) > 45;
  
          // determine minimum required angle to display full strafe range
          range_minangle = fabs(wishangle) % 90; // maximum range is 90 degree
          }
          state_strafekeys = strafekeys;
  
 -        if((keys & KEY_FORWARD) || (keys & KEY_BACKWARD) || swimming || autocvar__hud_configure)
 +        if((!strafekeys && vlen(vec2(movement)) > 0) || swimming || autocvar__hud_configure)
          {
              turn = false;
          }
                  {
                      turn = true; // CPMA turning
                      turnangle = wishangle;
 +
 +                    // calculate the maximum air strafe speed and acceleration
 +                    if(PHYS_MAXAIRSPEED(strafeplayer) == 0){
 +                        maxspeed = 0;
 +                    }
 +                    else if(PHYS_MAXAIRSTRAFESPEED(strafeplayer) == 0 || PHYS_MAXAIRSPEED(strafeplayer) <= PHYS_MAXAIRSTRAFESPEED(strafeplayer)){
 +                        maxspeed = PHYS_MAXAIRSPEED(strafeplayer);
 +                    }
 +                    else{
 +                        maxspeed = PHYS_MAXAIRSPEED(strafeplayer) * pow(fabs(PHYS_MAXAIRSTRAFESPEED(strafeplayer) / PHYS_MAXAIRSPEED(strafeplayer)), 1 - (90 - fabs(wishangle)) / 45); // no modifiers here because they don't affect air strafing
 +                    }
 +                    turnspeed = vlen(vec2(movement));
 +                    if(turnspeed == 0) turnspeed = maxspeed;
 +                    else turnspeed = min(turnspeed, maxspeed);
 +
 +                    if(PHYS_AIRACCELERATE(strafeplayer) == 0){
 +                        maxaccel = 0;
 +                    }
 +                    else if(PHYS_AIRSTRAFEACCELERATE(strafeplayer) == 0 || PHYS_AIRACCELERATE(strafeplayer) <= PHYS_AIRSTRAFEACCELERATE(strafeplayer)){
 +                        maxaccel = PHYS_AIRACCELERATE(strafeplayer);
 +                    }
 +                    else{
 +                        maxaccel = PHYS_AIRACCELERATE(strafeplayer) * pow(fabs(PHYS_AIRSTRAFEACCELERATE(strafeplayer) / PHYS_AIRACCELERATE(strafeplayer)), 1 - (90 - fabs(wishangle)) / 45); // no modifiers here because they don't affect air strafing
 +                    }
 +                    turnaccel = maxaccel;
                  }
              }
              else if((time - state_strafekeys_time) >= autocvar_hud_panel_strafehud_timeout_turn) // timeout for jumping with strafe keys only
                  turn = false;
              }
          }
 -        if(turn)
 +        if(turn && (onground || !strafekeys)) // retain last state until strafe turning times out
          {
 -            maxspeed = PHYS_MAXAIRSTRAFESPEED(strafeplayer); // no modifiers here because they don't affect air strafing
              wishangle = turnangle;
 +            movespeed = turnspeed;
 +            maxaccel = turnaccel;
 +        }
 +        else{
 +            movespeed = vlen(vec2(movement));
 +            if(movespeed == 0) movespeed = maxspeed;
 +            else movespeed = min(movespeed, maxspeed);
 +
 +            if(onground)
 +            {
 +                if((keys & KEY_JUMP) && ((time - state_onground_time) < autocvar_hud_panel_strafehud_timeout_ground)) // if ground timeout hasn't expired yet use air accelerate
 +                {
 +                    maxaccel = !autocvar__hud_configure ? PHYS_AIRACCELERATE(strafeplayer) * crouch_mod * water_mod : 1;
 +                }
 +            }
 +            else
 +            {
 +                if(!(keys & KEY_JUMP) && ((time - state_onground_time) < autocvar_hud_panel_strafehud_timeout_air)) // if air timeout hasn't expired yet use ground accelerate
 +                {
 +                    maxaccel = !autocvar__hud_configure ? PHYS_ACCELERATE(strafeplayer) * crouch_mod * water_mod : 1;
 +                }
 +            }
          }
  
 -        minspeed = autocvar_hud_panel_strafehud_switch_minspeed < 0 ? maxspeed + antiflicker_speed : autocvar_hud_panel_strafehud_switch_minspeed;
 +        minspeed = autocvar_hud_panel_strafehud_switch_minspeed < 0 ? (movespeed - maxaccel) + antiflicker_speed : autocvar_hud_panel_strafehud_switch_minspeed;
  
          // get current strafing angle ranging from -180° to +180°
          if(!autocvar__hud_configure)
                  // calculate view angle relative to the players current velocity direction
                  angle = vel_angle - view_angle;
  
 -                // if the angle goes above 180° or below -180° wrap it to the opposite side
 +                // if the angle goes above 180° or below -180° wrap it to the opposite side since we want the interior angle
                  if (angle > 180) angle -= 360;
                  else if(angle < -180) angle += 360;
  
 -                // shift the strafe angle by 180° for hud calculations
 -                if(angle < 0) angle += 180;
 -                else angle -= 180;
 -
                  // determine whether the player is strafing forwards or backwards
                  // if the player isn't strafe turning use forwards/backwards keys to determine direction
 -                if(!strafekeys)
 +                if(fabs(wishangle) != 90)
                  {
                      if(keys_fwd > 0)
                      {
 -                        state_fwd = true;
 +                        fwd = true;
                      }
                      else if(keys_fwd < 0)
                      {
 -                        state_fwd = false;
 +                        fwd = false;
                      }
                      else
                      {
 -                        state_fwd = fabs(angle) <= 90;
 +                        fwd = fabs(angle) <= 90;
                      }
                  }
                  // otherwise determine by examining the strafe angle
                  {
                      if(wishangle < 0) // detect direction using wishangle since the direction is not yet set
                      {
 -                        state_fwd = angle <= -wishangle;
 +                        fwd = angle <= -wishangle;
                      }
                      else
                      {
 -                        state_fwd = angle >= -wishangle;
 +                        fwd = angle >= -wishangle;
                      }
                  }
  
 -                if(state_fwd_prev != state_fwd)
 -                {
 -                    state_fwd_time = time;
 -                }
 -                state_fwd_prev = state_fwd;
 -
 -                if((time - state_fwd_time) >= autocvar_hud_panel_strafehud_timeout_direction || speed < maxspeed || (strafekeys && mode == 0)) // timeout when changing between forwards and backwards movement
 -                {
 -                    fwd = state_fwd;
 -                }
 -
                  // shift the strafe angle by 180° when strafing backwards
                  if(!fwd)
                  {
          }
  
          // best angle to strafe at
 -        bestangle = (speed > maxspeed ? acos(maxspeed / speed) : 0) * RAD2DEG * (direction < 0 ? -1 : 1);
 +        bestangle = (speed > (movespeed - maxaccel) ? acos((movespeed - maxaccel) / speed) : 0) * RAD2DEG * (direction < 0 ? -1 : 1);
 +        prebestangle = (speed > movespeed ? acos(movespeed / speed) : 0) * RAD2DEG * (direction < 0 ? -1 : 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 = max(panel_size.x * autocvar_hud_panel_strafehud_angle_width, 1);
 +        currentangle_size.x = autocvar_hud_panel_strafehud_angle_width;
 +        currentangle_size.y = autocvar_hud_panel_strafehud_angle_height;
 +        if(!autocvar_hud_panel_strafehud_uncapped)
 +        {
 +            currentangle_size.x = min(currentangle_size.x, 10);
 +            currentangle_size.y = min(currentangle_size.y, 10);
 +        }
 +        currentangle_size.x *= panel_size.x;
 +        currentangle_size.y *= panel_size.y;
 +        if(!autocvar_hud_panel_strafehud_uncapped)
 +        {
 +            currentangle_size.x = max(currentangle_size.x, 1);
 +            currentangle_size.y = max(currentangle_size.y, 1);
 +        }
 +        else
 +        {
 +            currentangle_size.y = max(currentangle_size.y, 0);
 +        }
          if(mode == 0)
          {
              currentangle_offset = angle/hudangle * panel_size.x;
          {
              currentangle_offset = bound(-hudangle/2, angle, hudangle/2)/hudangle * panel_size.x + panel_size.x/2;
          }
 -        currentangle_size.y = max(panel_size.y * min(autocvar_hud_panel_strafehud_angle_height, 2), 1);
          // best strafe acceleration angle
          bestangle_offset        =  bestangle/hudangle * panel_size.x + panel_size.x/2;
          switch_bestangle_offset = -bestangle/hudangle * panel_size.x + panel_size.x/2;
 -        bestangle_width = max(panel_size.x * autocvar_hud_panel_strafehud_switch_width, 1);
 +        bestangle_width = panel_size.x * autocvar_hud_panel_strafehud_switch_width;
 +        if(!autocvar_hud_panel_strafehud_uncapped)
 +            bestangle_width = max(bestangle_width, 1);
  
          if(((angle > -wishangle && direction < 0) || (angle < -wishangle && direction > 0)) && (direction != 0))
          {
              switch_odd_bestangle_offset = (odd_bestangle+bestangle*2)/hudangle * panel_size.x + panel_size.x/2;
          }
          // direction indicator
 -        direction_size_vertical.x = max(panel_size.y * min(autocvar_hud_panel_strafehud_direction_width, .5), 1);
 -        direction_size_vertical.y = panel_size.y;
 -        direction_size_horizontal.x = max(panel_size.x * min(autocvar_hud_panel_strafehud_direction_length, .5), direction_size_vertical.x);
 +        direction_size_vertical.x = autocvar_hud_panel_strafehud_direction_width;
 +        if(!autocvar_hud_panel_strafehud_uncapped)
 +            direction_size_vertical.x = min(direction_size_vertical.x, 1);
 +        direction_size_vertical.x *= panel_size.y;
 +        if(!autocvar_hud_panel_strafehud_uncapped)
 +            direction_size_vertical.x = max(direction_size_vertical.x, 1);
 +        direction_size_vertical.y = panel_size.y + direction_size_vertical.x*2;
 +        direction_size_horizontal.x = panel_size.x * min(autocvar_hud_panel_strafehud_direction_length, .5);
          direction_size_horizontal.y = direction_size_vertical.x;
          // overturn
          overturn_width = 180/hudangle * panel_size.x;
          else
          {
              // calculate various zones of the strafe-o-meter
 -            accelzone_width = overturn_offset = (90 - fabs(bestangle + wishangle))/hudangle * panel_size.x;
 -            accelzone_right_offset = 0;
 +            accelzone_width = (90 - fabs(bestangle + wishangle))/hudangle * panel_size.x;
 +            preaccelzone_width = (fabs(bestangle - prebestangle))/hudangle * panel_size.x;
 +            overturn_offset = accelzone_width + preaccelzone_width;
 +            accelzone_right_offset = preaccelzone_width;
              accelzone_left_offset = overturn_offset + overturn_width;
 -            neutral_width = 360/hudangle * panel_size.x - accelzone_width*2 - overturn_width;
 -            neutral_offset = direction < 0 ? accelzone_left_offset + accelzone_width : -neutral_width;
 -
 -            // remove switch indicator width from offset
 -            if(direction < 0)
 -            {
 -                bestangle_offset -= bestangle_width;
 -                switch_odd_bestangle_offset -= bestangle_width;
 -            }
 -            else
 -            {
 -                switch_bestangle_offset -= bestangle_width;
 -                odd_bestangle_offset -= bestangle_width;
 -            }
 +            preaccelzone_right_offset = 0;
 +            preaccelzone_left_offset = accelzone_left_offset + accelzone_width;
 +            neutral_width = 360/hudangle * panel_size.x - accelzone_width*2 - preaccelzone_width*2 - overturn_width;
 +            neutral_offset = direction < 0 ? preaccelzone_left_offset + preaccelzone_width : -neutral_width;
  
              // shift hud if operating in view angle centered mode
              if(mode == 0)
              neutral_offset += shift_offset;
              accelzone_left_offset += shift_offset;
              accelzone_right_offset += shift_offset;
 +            preaccelzone_left_offset += shift_offset;
 +            preaccelzone_right_offset += shift_offset;
              overturn_offset += shift_offset;
  
              // draw left acceleration zone
              HUD_Panel_DrawStrafeHUD(accelzone_left_offset, accelzone_width, autocvar_hud_panel_strafehud_bar_accel_color, autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 1);
 +            HUD_Panel_DrawStrafeHUD(preaccelzone_left_offset, preaccelzone_width, autocvar_hud_panel_strafehud_bar_preaccel_color, autocvar_hud_panel_strafehud_bar_preaccel_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 0);
  
              // draw right acceleration zone
              HUD_Panel_DrawStrafeHUD(accelzone_right_offset, accelzone_width, autocvar_hud_panel_strafehud_bar_accel_color, autocvar_hud_panel_strafehud_bar_accel_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 2);
 +            HUD_Panel_DrawStrafeHUD(preaccelzone_right_offset, preaccelzone_width, autocvar_hud_panel_strafehud_bar_preaccel_color, autocvar_hud_panel_strafehud_bar_preaccel_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 0);
  
              // draw overturn zone
              HUD_Panel_DrawStrafeHUD(overturn_offset, overturn_width, autocvar_hud_panel_strafehud_bar_overturn_color, autocvar_hud_panel_strafehud_bar_overturn_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 3);
              // draw neutral zone
              HUD_Panel_DrawStrafeHUD(neutral_offset, neutral_width, autocvar_hud_panel_strafehud_bar_neutral_color, autocvar_hud_panel_strafehud_bar_neutral_alpha * panel_fg_alpha, autocvar_hud_panel_strafehud_style, 0);
  
 -            if(direction != 0 && direction_size_vertical.x > 0 && autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha > 0)
 +            if(speed >= minspeed && bestangle_width > 0) // only draw indicators if minspeed is reached
              {
 -                bool indicator_direction = direction < 0;
 -                // invert left/right when strafing backwards or when strafing towards the opposite side indicated by the direction variable
 -                // if both conditions are true then it's inverted twice hence not inverted at all
 -                if(!fwd != odd_angles)
 +                // draw the switch indicator(s)
 +                float offset = !odd_angles ? bestangle_offset : odd_bestangle_offset;
 +                float switch_offset = !odd_angles ? switch_bestangle_offset : switch_odd_bestangle_offset;
 +
 +                // remove switch indicator width from offset
 +                if(direction < 0)
                  {
 -                    indicator_direction = !indicator_direction;
 +                    if(!odd_angles)
 +                        offset -= bestangle_width;
 +                    else
 +                        switch_offset -= bestangle_width;
 +                }
 +                else
 +                {
 +                    if(!odd_angles)
 +                        switch_offset -= bestangle_width;
 +                    else
 +                        offset -= bestangle_width;
                  }
 -                // draw the direction indicator caps at the sides of the hud
 -                // vertical line
 -                if(direction_size_vertical.y > 0) drawfill(panel_pos + eX * (indicator_direction ? -direction_size_vertical.x : panel_size.x), direction_size_vertical, autocvar_hud_panel_strafehud_direction_color, autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 -                // top horizontal line
 -                drawfill(panel_pos + eX * (indicator_direction ? -direction_size_vertical.x : panel_size.x - direction_size_horizontal.x + direction_size_vertical.x) - eY * direction_size_horizontal.y, direction_size_horizontal, autocvar_hud_panel_strafehud_direction_color, autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 -                // bottom horizontal line
 -                drawfill(panel_pos + eX * (indicator_direction ? -direction_size_vertical.x : panel_size.x - direction_size_horizontal.x + direction_size_vertical.x) + eY * panel_size.y, direction_size_horizontal, autocvar_hud_panel_strafehud_direction_color, autocvar_hud_panel_strafehud_direction_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 -            }
  
 -            if(speed >= minspeed) // only draw indicators if minspeed is reached
 -            {
 -                // draw best angles for acceleration
 -                float offset = !odd_angles ? bestangle_offset : odd_bestangle_offset;
 -                float switch_offset = !odd_angles ? switch_bestangle_offset : switch_odd_bestangle_offset;
 -                // both indicators are inactive if no direction can be determined
 -                vector switch_color = direction != 0 ? autocvar_hud_panel_strafehud_switch_active_color : autocvar_hud_panel_strafehud_switch_inactive_color;
 -                float switch_alpha = direction != 0 ? autocvar_hud_panel_strafehud_switch_active_alpha : autocvar_hud_panel_strafehud_switch_inactive_alpha;
 -                // draw the switch indicators
 -                HUD_Panel_DrawStrafeHUD(switch_offset, bestangle_width, autocvar_hud_panel_strafehud_switch_inactive_color, autocvar_hud_panel_strafehud_switch_inactive_alpha * panel_fg_alpha, 0, 0);
 -                HUD_Panel_DrawStrafeHUD(offset, bestangle_width, switch_color, switch_alpha * panel_fg_alpha, 0, 0);
 +                HUD_Panel_DrawStrafeHUD(switch_offset, bestangle_width, autocvar_hud_panel_strafehud_switch_color, autocvar_hud_panel_strafehud_switch_alpha * panel_fg_alpha, 0, 0);
 +                if(direction == 0) HUD_Panel_DrawStrafeHUD(offset, bestangle_width, autocvar_hud_panel_strafehud_switch_color, autocvar_hud_panel_strafehud_switch_alpha * panel_fg_alpha, 0, 0);
              }
          }
  
 -        // experimental: slick detector
 -        slickdetector_height = panel_size.y * bound(0, autocvar_hud_panel_strafehud_slickdetector_height, 0.5);
 -        if(autocvar_hud_panel_strafehud_slickdetector_range > 0 && autocvar_hud_panel_strafehud_slickdetector_alpha > 0 && slickdetector_height > 0 && panel_size.x > 0) // dunno if slick detection works in spectate
 +        // slick detector
 +        slickdetector_height = max(autocvar_hud_panel_strafehud_slickdetector_height, 0);
 +        if(!autocvar_hud_panel_strafehud_uncapped)
 +             slickdetector_height = min(slickdetector_height, 1);
 +        slickdetector_height *= panel_size.y;
 +        if(autocvar_hud_panel_strafehud_slickdetector_range > 0 && autocvar_hud_panel_strafehud_slickdetector_alpha > 0 && slickdetector_height > 0 && panel_size.x > 0)
          {
 -            float slicksteps = 90 / pow(2, bound(0, autocvar_hud_panel_strafehud_slickdetector_granularity, 4));
 +            float slicksteps = max(autocvar_hud_panel_strafehud_slickdetector_granularity, 0);
              bool slickdetected = false;
  
 -            slickdetected = IS_ONSLICK(strafeplayer); // don't need to traceline if already touching slick
 +            if(!autocvar_hud_panel_strafehud_uncapped)
 +                slicksteps = min(slicksteps, 4);
 +            slicksteps = 90 / pow(2, slicksteps);
 +
 +            if(islocal) slickdetected = IS_ONSLICK(strafeplayer); // don't need to traceline if already touching slick
  
              // traceline into every direction
              trace_dphitq3surfaceflags = 0;
                          slickoffset.y = cos(j * DEG2RAD) * slickrotate;
  
                          traceline(strafeplayer.origin, strafeplayer.origin + slickoffset, MOVE_WORLDONLY, NULL);
 -                        if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) slickdetected = true;
 +                        if((PHYS_FRICTION(strafeplayer) == 0 && trace_fraction < 1) || trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) slickdetected = true;
                      }
                  }
                  else
                  {
                      slickoffset.x = slickoffset.y = 0;
                      traceline(strafeplayer.origin, strafeplayer.origin + slickoffset, MOVE_WORLDONLY, NULL);
 -                    if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) slickdetected = true;
 +                    if((PHYS_FRICTION(strafeplayer) == 0 && trace_fraction < 1) || trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK) slickdetected = true;
                  }
              }
  
              }
          }
  
 +        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 - 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);
 +        }
 +
 +        if(speed < ((movespeed - maxaccel) + 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);
 +            }
 +        }
 +
 +        float angleheight_offset = currentangle_size.y;
 +        float ghost_offset = 0;
 +        if(autocvar_hud_panel_strafehud_bestangle && direction != 0)
 +        {
 +            ghost_offset = !odd_angles ? bestangle_offset : odd_bestangle_offset;
 +            if(ghost_offset < 0) ghost_offset = 0;
 +            if(ghost_offset > panel_size.x) ghost_offset = panel_size.x;
 +        }
 +
 +        switch(autocvar_hud_panel_strafehud_angle_style)
 +        {
 +            case 1:
 +                if(currentangle_size.x > 0 && currentangle_size.y > 0)
 +                {
 +                    if(autocvar_hud_panel_strafehud_bestangle && direction != 0) drawfill(panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2) + eX * (ghost_offset - currentangle_size.x/2), currentangle_size, autocvar_hud_panel_strafehud_bestangle_color, autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 +                    drawfill(panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2) + eX * (currentangle_offset - currentangle_size.x/2), currentangle_size, currentangle_color, autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 +                }
 +                break;
 +            case 2:
 +                if(currentangle_size.x > 0 && currentangle_size.y > 0)
 +                {
 +                    vector line_size = currentangle_size;
 +                    line_size.y = currentangle_size.y / (bound(2, autocvar_hud_panel_strafehud_angle_dashes, currentangle_size.y)*2-1);
 +                    for(float i = 0; i < currentangle_size.y; i += line_size.y*2)
 +                    {
 +                        if(i + line_size.y*2 >= currentangle_size.y) line_size.y = currentangle_size.y - i;
 +                        if(autocvar_hud_panel_strafehud_bestangle && direction != 0) drawfill(panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2 - i) + eX * (ghost_offset - line_size.x/2), line_size, autocvar_hud_panel_strafehud_bestangle_color, autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 +                        drawfill(panel_pos - eY * ((currentangle_size.y - panel_size.y) / 2 - i) + eX * (currentangle_offset - line_size.x/2), line_size, currentangle_color, autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 +                    }
 +                }
 +                break;
 +            case 0:
 +            default:
 +                // don't offset text and arrows if the angle indicator line isn't drawn
 +                angleheight_offset = panel_size.y;
 +        }
 +
 +        if(autocvar_hud_panel_strafehud_angle_arrow > 0)
 +        {
 +            if(arrow_size > 0)
 +            {
 +                if(autocvar_hud_panel_strafehud_angle_arrow == 1 || autocvar_hud_panel_strafehud_angle_arrow >= 3)
 +                {
 +                    if(autocvar_hud_panel_strafehud_bestangle && direction != 0) StrafeHUD_drawStrafeArrow(panel_pos + eY * ((panel_size.y - angleheight_offset) / 2) + eX * ghost_offset, arrow_size, autocvar_hud_panel_strafehud_bestangle_color, autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha, true);
 +                    StrafeHUD_drawStrafeArrow(panel_pos + eY * ((panel_size.y - angleheight_offset) / 2) + eX * currentangle_offset, arrow_size, currentangle_color, autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, true);
 +                }
 +                if(autocvar_hud_panel_strafehud_angle_arrow >= 2)
 +                {
 +                    if(autocvar_hud_panel_strafehud_bestangle && direction != 0) StrafeHUD_drawStrafeArrow(panel_pos + eY * ((panel_size.y - angleheight_offset) / 2 + angleheight_offset) + eX * ghost_offset, arrow_size, autocvar_hud_panel_strafehud_bestangle_color, autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha, false);
 +                    StrafeHUD_drawStrafeArrow(panel_pos + eY * ((panel_size.y - angleheight_offset) / 2 + angleheight_offset) + eX * currentangle_offset, arrow_size, currentangle_color, autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha, false);
 +                }
 +            }
 +        }
 +
          draw_beginBoldFont();
          // show speed when crossing the start trigger
          if(autocvar_hud_panel_strafehud_startspeed_fade > 0)
          {
              float text_alpha = 0;
 -            if(race_checkpoint == 254) // checkpoint 254 is the start trigger
 +            if((race_nextcheckpoint == 1) || (race_checkpoint == 254 && race_nextcheckpoint == 255)) // check if the start trigger was hit (will also trigger if the finish trigger was hit if those have the same ID)
              {
                  if(starttime != race_checkpointtime)
                  {
              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);
 +                startspeed_size.y = autocvar_hud_panel_strafehud_startspeed_size;
 +                if(!autocvar_hud_panel_strafehud_uncapped)
 +                    startspeed_size.y = min(startspeed_size.y, 10);
 +                startspeed_size.y *= panel_size.y;
 +                if(!autocvar_hud_panel_strafehud_uncapped)
 +                    startspeed_size.y = max(startspeed_size.y, 1);
 +
 +                float text_offset = 0;
 +                if((autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha > 0) || (autocvar_hud_panel_strafehud_bestangle && autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha > 0))
 +                {
 +                    text_offset = (angleheight_offset - panel_size.y) / 2;
 +                    if(arrow_size > 0 && autocvar_hud_panel_strafehud_angle_arrow >= 2)
 +                        text_offset += arrow_size;
 +                    // make sure text doesn't draw inside the strafehud bar
 +                    text_offset = max(text_offset, 0);
 +                }
 +
+                 string speed_unit = GetSpeedUnit(autocvar_hud_panel_strafehud_unit);
 -                drawstring_aspect(panel_pos + eY * panel_size.y, 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);
 +                drawstring_aspect(panel_pos + eY * (panel_size.y + text_offset), strcat(ftos_decimals(startspeed * speed_conversion_factor, 2), autocvar_hud_panel_strafehud_unit_show ? speed_unit : ""), startspeed_size, autocvar_hud_panel_strafehud_startspeed_color, text_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
              }
          }
          else
              float jumpheight_min = max(autocvar_hud_panel_strafehud_jumpheight_min, 0);
              float jumpheight_current = strafeplayer.origin.z;
              float jumpspeed_current = strafeplayer.velocity.z;
 -            if(jumpspeed_prev <= jumpspeed_current || jumpheight_prev > jumpheight_current || IS_ONGROUND(strafeplayer) || swimming || IS_DEAD(strafeplayer) || spectating)
 +            if(jumpspeed_prev <= jumpspeed_current || jumpheight_prev > jumpheight_current || onground || swimming || IS_DEAD(strafeplayer) || spectating)
              {
                  // tries to catch kill and spectate but those are not reliable, should just hook to kill/spectate/teleport and reset jump height there
                  jumprestart = true;
              if(jumpheight_persistent > 0 && text_alpha > 0 && autocvar_hud_panel_strafehud_jumpheight_size > 0)
              {
                  vector jumpheight_size = panel_size;
 -                jumpheight_size.y = panel_size.y * min(autocvar_hud_panel_strafehud_jumpheight_size, 5);
 +                jumpheight_size.y = autocvar_hud_panel_strafehud_jumpheight_size;
 +                if(!autocvar_hud_panel_strafehud_uncapped)
 +                    jumpheight_size.y = min(jumpheight_size.y, 10);
 +                jumpheight_size.y *= panel_size.y;
 +                if(!autocvar_hud_panel_strafehud_uncapped)
 +                    jumpheight_size.y = max(jumpheight_size.y, 1);
 +
 +                float text_offset = 0;
 +                if((autocvar_hud_panel_strafehud_angle_alpha * panel_fg_alpha > 0) || (autocvar_hud_panel_strafehud_bestangle && autocvar_hud_panel_strafehud_bestangle_alpha * panel_fg_alpha > 0))
 +                {
 +                    text_offset = (angleheight_offset - panel_size.y) / 2;
 +                    if(arrow_size > 0 && autocvar_hud_panel_strafehud_angle_arrow == 1 || autocvar_hud_panel_strafehud_angle_arrow >= 3)
 +                        text_offset += arrow_size;
 +                    // make sure text doesn't draw inside the strafehud bar
 +                    text_offset = max(text_offset, 0);
 +                }
 +
+                 string length_unit = GetLengthUnit(autocvar_hud_panel_strafehud_unit);
 -                drawstring_aspect(panel_pos - eY * jumpheight_size.y, strcat(ftos_decimals(jumpheight_persistent * length_conversion_factor, 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);
 +                drawstring_aspect(panel_pos - eY * (jumpheight_size.y + text_offset), strcat(ftos_decimals(jumpheight_persistent * length_conversion_factor, 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);
              }
          }
          else
              jumpheight = jumpheight_persistent = -1;
          }
          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);
 -        }
      }
  }
  
@@@ -943,11 -780,9 +943,11 @@@ void HUD_Panel_DrawStrafeHUD(float offs
      float mirror_offset, mirror_width;
      vector size = panel_size;
      vector mirror_size = panel_size;
 +    int gradient_start;
 +    float gradient_offset, gradient_mirror_offset;
 +    float overflow_width = 0, overflow_mirror_width = 0;
  
      float original_width = width;
 -    float hiddencolor_width;
  
      if(alpha <= 0 && type != 2 || width <= 0) return;
  
          mirror_width = min(offset + width - panel_size.x - hidden_width, width);
          mirror_offset = max(offset - panel_size.x - hidden_width, 0);
      }
 +
 +    if(width < 0) width = 0;
      if((offset + width) > panel_size.x)
      {
 +        overflow_width = (offset + width) - panel_size.x;
          width = panel_size.x - offset;
      }
      if(mirror_offset < 0)
          mirror_width += mirror_offset;
          mirror_offset = 0;
      }
 +
 +    if(mirror_width < 0) mirror_width = 0;
      if((mirror_offset + mirror_width) > panel_size.x)
      {
 +        overflow_mirror_width = (mirror_offset + mirror_width) - panel_size.x;
          mirror_width = panel_size.x - mirror_offset;
      }
  
 -    if(width < 0) width = 0;
 -    if(mirror_width < 0) mirror_width = 0;
 -    hiddencolor_width = original_width - width - mirror_width;
 -
      if(direction < 0) // swap mirror and non-mirror values if direction points left
      {
          offset += mirror_offset;
          width += mirror_width;
          mirror_width = width - mirror_width;
          width -= mirror_width;
 +
 +        overflow_width += overflow_mirror_width;
 +        overflow_mirror_width = overflow_width - overflow_mirror_width;
 +        overflow_width -= overflow_mirror_width;
      }
  
      size.x = width;
              break;
  
          case 2: // gradient style (types: 1 = left, 2 = right, 3 = both)
 -            StrafeHUD_drawGradient(color, autocvar_hud_panel_strafehud_bar_neutral_color, mirror_size, original_width, mirror_offset, alpha, width + (mirror_offset == 0 ? hiddencolor_width : 0), gradientType);
 -            StrafeHUD_drawGradient(color, autocvar_hud_panel_strafehud_bar_neutral_color, size, original_width, offset, alpha, (offset == 0 ? hiddencolor_width : 0), gradientType);
 +            // determine whether the gradient starts in the mirrored or the non-mirrored area
 +            if(offset == 0 && mirror_offset == 0) gradient_start = width > mirror_width ? 2 : 1;
 +            else if(offset == 0) gradient_start = 2;
 +            else if(mirror_offset == 0) gradient_start = 1;
 +            else gradient_start = 0;
 +
 +            switch(gradient_start){
 +                default:
 +                case 0: // no offset required
 +                    gradient_offset = gradient_mirror_offset = 0;
 +                    break;
 +                case 1: // offset starts in non-mirrored area, mirrored area requires offset
 +                    gradient_offset = 0;
 +                    gradient_mirror_offset = original_width - (mirror_width + overflow_mirror_width);
 +                    break;
 +                case 2: // offset starts in mirrored area, non-mirrored area requires offset
 +                    gradient_offset = original_width - (width + overflow_width);
 +                    gradient_mirror_offset = 0;
 +            }
 +
 +            StrafeHUD_drawGradient(color, autocvar_hud_panel_strafehud_bar_neutral_color, mirror_size, original_width, mirror_offset, alpha, gradient_mirror_offset, gradientType);
 +            StrafeHUD_drawGradient(color, autocvar_hud_panel_strafehud_bar_neutral_color, size, original_width, offset, alpha, gradient_offset, gradientType);
      }
  }
  
@@@ -1076,41 -885,30 +1076,41 @@@ void StrafeHUD_drawGradient(vector colo
      }
  }
  
 +// draw the strafe arrows (inspired by drawspritearrow() in common/mutators/mutator/waypoints/waypointsprites.qc)
 +void StrafeHUD_drawStrafeArrow(vector origin, float size, vector color, float alpha, bool flipped)
 +{
 +    if(flipped) origin -= size*eY;
 +    R_BeginPolygon("", DRAWFLAG_NORMAL, true);
 +    R_PolygonVertex(origin + (flipped ? size*eY : '0 0 0')          , '0 0 0', color, alpha);
 +    R_PolygonVertex(origin + (flipped ? '0 0 0' : size*eY) - size*eX, '0 0 0', color, alpha);
 +    R_PolygonVertex(origin + (flipped ? '0 0 0' : size*eY) + size*eX, '0 0 0', color, alpha);
 +    R_EndPolygon();
 +}
 +
  // length unit conversion (km and miles are only included to match the GetSpeedUnit* functions)
  float GetLengthUnitFactor(int length_unit)
  {
 -      switch(length_unit)
 -      {
 -              default:
 -              case 1: return 1.0;
 -              case 2: return 0.0254;
 -              case 3: return 0.0254 * 0.001;
 -              case 4: return 0.0254 * 0.001 * 0.6213711922;
 -              case 5: return 0.0254 * 0.001 * 0.5399568035;
 -      }
 +    switch(length_unit)
 +    {
 +        default:
 +        case 1: return 1.0;
 +        case 2: return 0.0254;
 +        case 3: return 0.0254 * 0.001;
 +        case 4: return 0.0254 * 0.001 * 0.6213711922;
 +        case 5: return 0.0254 * 0.001 * 0.5399568035;
 +    }
  }
  
  string GetLengthUnit(int length_unit)
  {
 -      switch(length_unit)
 -      {
 -              // translator-friendly strings without the initial space
 -              default:
 -              case 1: return strcat(" ", _("qu"));
 -              case 2: return strcat(" ", _("m"));
 -              case 3: return strcat(" ", _("km"));
 -              case 4: return strcat(" ", _("mi"));
 -              case 5: return strcat(" ", _("nmi"));
 -      }
 +    switch(length_unit)
 +    {
 +        // translator-friendly strings without the initial space
 +        default:
 +        case 1: return strcat(" ", _("qu"));
 +        case 2: return strcat(" ", _("m"));
 +        case 3: return strcat(" ", _("km"));
 +        case 4: return strcat(" ", _("mi"));
 +        case 5: return strcat(" ", _("nmi"));
 +    }
  }