]> 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 --combined _hud_common.cfg
index ed53ab3ec37a9ce61f45fab82eabf5305a07a86b,6339895eafd312488b037769801bb5611e7e5854..0028a732265d8f9dfec02bf3b604ed8a38a88a27
@@@ -19,6 -19,8 +19,8 @@@ seta hud_colorset_kill_2 "3" "similar t
  seta hud_colorset_kill_3 "4" "'good' or 'beneficial' text (you fragging someone, etc)"
  seta hud_colorset_background "7" "neutral/unimportant text"
  
+ seta hud_speed_unit "1" "speed unit (1 = qu/s, 2 = m/s, 3 = km/h, 4 = mph, 5 = knots)"
  // general hud panel cvars (i.e. shouldn't be adjusted by a skin config)
  seta hud_panel_weapons          1 "enable this panel"
  seta hud_panel_ammo             1 "enable this panel"
@@@ -49,6 -51,7 +51,7 @@@ seta hud_panel_scoreboard_accuracy 1 "s
  seta hud_panel_scoreboard_ctf_leaderboard 1 "show a capture time rankings leaderboard in the scoreboard if allowed by the server"
  seta hud_panel_scoreboard_itemstats 1 "show item stats panel in the scoreboard"
  seta hud_panel_strafehud        3 "enable this panel, 1 = show if not observing, 2 = show always, 3 = show only in race/cts if not observing"
+ seta hud_panel_pickup           1 "enable this panel"
  
  seta hud_panel_weapons_dynamichud          1 "apply the dynamic hud effects to this panel"
  seta hud_panel_ammo_dynamichud             1 "apply the dynamic hud effects to this panel"
@@@ -69,6 -72,7 +72,7 @@@ seta hud_panel_centerprint_dynamichu
  seta hud_panel_itemstime_dynamichud        1 "apply the dynamic hud effects to this panel"
  seta hud_panel_scoreboard_dynamichud       0 "apply the dynamic hud effects to this panel"
  seta hud_panel_strafehud_dynamichud        1 "apply the dynamic hud effects to this panel"
+ seta hud_panel_pickup_dynamichud           1 "apply the dynamic hud effects to this panel"
  
  seta hud_panel_weapons_ammo_full_shells 60 "show 100% of the status bar at this ammo count"
  seta hud_panel_weapons_ammo_full_nails 320 "show 100% of the status bar at this ammo count"
@@@ -104,15 -108,17 +108,17 @@@ seta hud_panel_engineinfo_framecounter_
  
  seta hud_panel_physics_acceleration_movingaverage 1 "use an averaging method for calculating acceleration instead of the real value"
  seta hud_panel_physics_update_interval 0.016 "how often (in seconds) numeric values get updated on screen"
- seta hud_panel_physics_speed_unit "1" "speed unit (1 = qu/s, 2 = m/s, 3 = km/h, 4 = mph, 5 = knots)"
  
  seta hud_panel_itemstime_progressbar_maxtime "30" "when left time is at least this amount, the status bar is full"
  seta hud_panel_itemstime_hidespawned "1" "if 1 hide an item from the panel when all the occurrences of it are available again; if 2 hide it when at least one occurrence is available again"
  seta hud_panel_itemstime_hidebig "0" "if 1 hide big armor and health from the panel"
  
+ set _hud_panel_quickmenu_file_from_server "" "reserved cvar set by the server with a custom server quickmenu file that appears in the default quickmenu"
  seta hud_panel_quickmenu_file "" "load the quick menu from this file (empty or 0 to disable)"
  seta hud_panel_quickmenu_translatecommands 0 "when the game is translated, translate strings inside commands too (useful for chat commands)"
  seta hud_panel_quickmenu_time 5 "quickmenu expires after this number of seconds in the same page"
+ // do not remember this setting
+ set hud_panel_quickmenu_server_is_default 0 "make the server's custom quickmenu the default one"
  
  seta hud_panel_infomessages_group0 1 "show group 0 messages (showing keys for non-crucial actions you can do while spectating/observing)"
  seta hud_panel_infomessages_group_time 6 "number of seconds a message of a group lasts before it gets changed"
@@@ -150,60 -156,49 +156,59 @@@ 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)"
  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 "1" "set to \"1\" to enable the slick detector which notifies you if there is slick near you"
 +seta hud_panel_strafehud_slickdetector_range "200" "range of the slick detector in qu"
 +seta hud_panel_strafehud_slickdetector_granularity "1" "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 "1" "set to \"1\" to enable the start speed indicator which shows you the speed you had while passing the start trigger of a race map"
 +seta hud_panel_strafehud_startspeed_fade "4" "fade time (in seconds) of the start speed text"
  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"
 +seta hud_panel_strafehud_jumpheight "0" "set to \"1\" to enable the jump height indicator which tells you how high you jumped"
 +seta hud_panel_strafehud_jumpheight_fade "4" "fade time (in seconds) of the jump height text"
  seta hud_panel_strafehud_jumpheight_min "50" "minimum jump height to display in the selected unit"
  seta hud_panel_strafehud_jumpheight_color "0 1 0.75" "color of the jump height text"
  seta hud_panel_strafehud_jumpheight_size "1.5" "size of the jump height text (relative to the panel height)"
 -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_ground "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_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)"
 +seta hud_panel_strafehud_fps_update "0.5" "update interval (in seconds) of the frametime to calculate the optimal angle, smaller values may cause flickering"
  
  // hud panel aliases
  alias quickmenu "cl_cmd hud quickmenu ${* ?}"
@@@ -284,3 -279,8 +289,8 @@@ seta hud_shownames_maxdistance 5000 "al
  seta hud_shownames_antioverlap 1 "if two tags overlap, fade out the one further away from you"
  seta hud_shownames_antioverlap_minalpha 0.4 "fade out overlapping tags to this alpha value"
  seta hud_shownames_offset 52 "offset (along z-axis) tag from player origin by this many units"
+ seta hud_panel_pickup_showtimer 1 "0 = hide timer, 1 = show timer, 2 = only when spectating"
+ seta hud_panel_pickup_iconsize 1.5 "icon size scale"
+ seta hud_panel_pickup_time 3 "pickup message duration (can't be higher than 5)"
+ seta hud_panel_pickup_fade_out 0.15 "how long a pickup message takes to fade out (this time is included in the message duration)"
index c82a6c779ee53065cf289e339c3ca0ec036797f1,46e8b7631b4eb7d3529f622c491cf9222ae00e86..075dfb3711bed22b957f3723c44e65ae06865f56
  #include "strafehud.qh"
  
  #include <client/draw.qh>
 -#include <client/hud/panel/racetimer.qh>
 -#include <client/view.qh>
 -#include <common/animdecide.qh>
 -#include <common/ent_cs.qh>
 -#include <common/mapinfo.qh>
 -#include <common/physics/movetypes/movetypes.qh>
 -#include <common/physics/player.qh>
 -#include <common/resources/cl_resources.qh>
  #include <lib/csqcmodel/cl_player.qh>
 +#include <common/physics/player.qh>
 +#include <common/physics/movetypes/movetypes.qh>
 +
 +// non-essential
 +#include <client/view.qh> // for v_flipped state
 +
 +// non-local players
 +#include <common/animdecide.qh> // anim_implicit_state
 +#include <common/ent_cs.qh> // CSQCModel_server2csqc()
 +
 +// start speed
 +#include <client/hud/panel/racetimer.qh> // checkpoint information (race_*)
 +
 +// jump height
 +#include <lib/csqcmodel/common.qh> // for IS_PLAYER() macro
 +#include <common/resources/cl_resources.qh> // IS_DEAD() macro
  
  // StrafeHUD (#25)
  
  void HUD_StrafeHUD_Export(int fh)
  {
 -    // allow saving cvars that aesthetically change the panel into hud skin files
 +      // allow saving cvars that aesthetically change the panel into hud skin files
  }
  
 -float hidden_width;
 -int direction;
 -float demo_angle = -37;
 -float demo_direction = 1;
 -float demo_time = 0;
 -bool state_onground = false;
 -float state_onground_time = 0;
 -bool state_strafekeys = false;
 -float state_strafekeys_time = 0;
 -bool turn = false;
 -float turnangle;
 -bool fwd = true;
 -bool state_fwd = true;
 -bool state_fwd_prev = true;
 -float state_fwd_time = 0;
 -float starttime = 0;
 -float startspeed = -1;
 -
 -// provide basic panel cvars to old clients
 -// TODO remove them after a future release (0.8.2+)
 -noref string autocvar_hud_panel_strafehud_pos = "0.320000 0.570000";
 -noref string autocvar_hud_panel_strafehud_size = "0.360000 0.020000";
 -noref string autocvar_hud_panel_strafehud_bg = "0";
 -noref string autocvar_hud_panel_strafehud_bg_color = "";
 -noref string autocvar_hud_panel_strafehud_bg_color_team = "";
 -noref string autocvar_hud_panel_strafehud_bg_alpha = "0.7";
 -noref string autocvar_hud_panel_strafehud_bg_border = "";
 -noref string autocvar_hud_panel_strafehud_bg_padding = "";
 +float GeomLerp(float a, float _lerp, float b); // declare GeomLerp here since there's no header file for it
  
  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
 -void HUD_Panel_DrawStrafeHUD(float offset, float width, vector color, float alpha, int type, int gradientType)
 +void HUD_Panel_DrawStrafeHUD(float offset, float width, float hidden_width, vector color, float alpha, int type, int gradientType)
  {
 -    float mirror_offset, mirror_width;
 -    vector size = panel_size;
 -    vector mirror_size = panel_size;
 -
 -    float original_width = width;
 -    float hiddencolor_width;
 -
 -    if(alpha <= 0 && type != 2 || width <= 0) return;
 -
 -    if(type == 2 && gradientType == 0) type = 0;
 -
 -    if(offset < 0)
 -    {
 -        mirror_width = min(fabs(offset), width);
 -        mirror_offset = panel_size.x + hidden_width - fabs(offset);
 -        width += offset;
 -        offset = 0;
 -    }
 -    else
 -    {
 -        mirror_width = min(offset + width - panel_size.x - hidden_width, width);
 -        mirror_offset = max(offset - panel_size.x - hidden_width, 0);
 -    }
 -    if((offset + width) > panel_size.x)
 -    {
 -        width = panel_size.x - offset;
 -    }
 -    if(mirror_offset < 0)
 -    {
 -        mirror_width += mirror_offset;
 -        mirror_offset = 0;
 -    }
 -    if((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;
 -        mirror_offset = offset - mirror_offset;
 -        offset -= mirror_offset;
 -
 -        width += mirror_width;
 -        mirror_width = width - mirror_width;
 -        width -= mirror_width;
 -    }
 -
 -    size.x = width;
 -    mirror_size.x = mirror_width;
 -
 -    switch(type)
 -    {
 -        default:
 -        case 0: // no styling (drawfill)
 -            if(mirror_size.x > 0 && mirror_size.y > 0) drawfill(panel_pos + eX * mirror_offset, mirror_size, color, alpha, DRAWFLAG_NORMAL);
 -            if(size.x > 0 && size.y > 0) drawfill(panel_pos + eX * offset, size, color, alpha, DRAWFLAG_NORMAL);
 -            break;
 -
 -        case 1: // progress bar style
 -            if(mirror_size.x > 0 && mirror_size.y > 0) HUD_Panel_DrawProgressBar(panel_pos + eX * mirror_offset, mirror_size, "progressbar", 1, 0, 0, color, alpha, DRAWFLAG_NORMAL);
 -            if(size.x > 0 && size.y > 0) HUD_Panel_DrawProgressBar(panel_pos + eX * offset, size, "progressbar", 1, 0, 0, color, alpha, DRAWFLAG_NORMAL);
 -            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);
 -    }
 +      float mirror_offset, mirror_width;
 +      vector size = panel_size;
 +      vector mirror_size = panel_size;
 +      float overflow_width = 0, overflow_mirror_width = 0;
 +      float original_width = width; // required for gradient
 +
 +      if(type == STRAFEHUD_STYLE_GRADIENT && gradientType == STRAFEHUD_GRADIENT_NONE)
 +              type = STRAFEHUD_STYLE_DRAWFILL;
 +
 +      if(alpha <= 0 && type != STRAFEHUD_STYLE_GRADIENT || width <= 0)
 +              return;
 +
 +      if(offset < 0)
 +      {
 +              mirror_width = min(fabs(offset), width);
 +              mirror_offset = panel_size.x + hidden_width - fabs(offset);
 +              width += offset;
 +              offset = 0;
 +      }
 +      else
 +      {
 +              mirror_width = min(offset + width - panel_size.x - hidden_width, width);
 +              mirror_offset = max(offset - panel_size.x - hidden_width, 0);
 +      }
 +
 +      width = max(width, 0);
 +      if((offset + width) > panel_size.x)
 +      {
 +              overflow_width = (offset + width) - panel_size.x;
 +              width = panel_size.x - offset;
 +      }
 +      size.x = width;
 +
 +      if(mirror_offset < 0)
 +      {
 +              mirror_width += mirror_offset;
 +              mirror_offset = 0;
 +      }
 +
 +      mirror_width = max(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;
 +      }
 +      mirror_size.x = mirror_width;
 +
 +      switch(type)
 +      {
 +              default:
 +              case STRAFEHUD_STYLE_DRAWFILL: // no styling (drawfill)
 +                      if(mirror_size.x > 0 && mirror_size.y > 0)
 +                              drawfill(panel_pos + eX * mirror_offset, mirror_size, color, alpha, DRAWFLAG_NORMAL);
 +                      if(size.x > 0 && size.y > 0)
 +                              drawfill(panel_pos + eX * offset, size, color, alpha, DRAWFLAG_NORMAL);
 +                      break;
 +
 +              case STRAFEHUD_STYLE_PROGRESSBAR: // progress bar style
 +                      if(mirror_size.x > 0 && mirror_size.y > 0)
 +                              HUD_Panel_DrawProgressBar(
 +                                      panel_pos + eX * mirror_offset,
 +                                      mirror_size, "progressbar",
 +                                      1, 0, 0, color, alpha, DRAWFLAG_NORMAL);
 +                      if(size.x > 0 && size.y > 0)
 +                              HUD_Panel_DrawProgressBar(
 +                                      panel_pos + eX * offset,
 +                                      size, "progressbar",
 +                                      1, 0, 0, color, alpha, DRAWFLAG_NORMAL);
 +                      break;
 +
 +              case STRAFEHUD_STYLE_GRADIENT: // gradient style (types: 1 = left, 2 = right, 3 = both)
 +                      // determine whether the gradient starts in the mirrored or the non-mirrored area
 +                      int gradient_start;
 +                      float gradient_offset, gradient_mirror_offset;
 +
 +                      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);
 +      }
  }
  
  vector StrafeHUD_mixColors(vector color1, vector color2, float ratio)
  {
 -    vector mixedColor;
 -    if(ratio <= 0) return color1;
 -    if(ratio >= 1) return color2;
 -    mixedColor.x = color1.x + (color2.x - color1.x) * ratio;
 -    mixedColor.y = color1.y + (color2.y - color1.y) * ratio;
 -    mixedColor.z = color1.z + (color2.z - color1.z) * ratio;
 -    return mixedColor;
 +      vector mixedColor;
 +      if(ratio <= 0) return color1;
 +      if(ratio >= 1) return color2;
 +      mixedColor.x = color1.x + (color2.x - color1.x) * ratio;
 +      mixedColor.y = color1.y + (color2.y - color1.y) * ratio;
 +      mixedColor.z = color1.z + (color2.z - color1.z) * ratio;
 +      return mixedColor;
  }
  
  void StrafeHUD_drawGradient(vector color1, vector color2, vector size, float original_width, float offset, float alpha, float gradientOffset, int gradientType)
  {
 -    float color_ratio, alpha1, alpha2;
 -    vector gradient_size = size;
 -    alpha1 = bound(0, alpha, 1);
 -    alpha2 = bound(0, autocvar_hud_panel_strafehud_bar_neutral_alpha, 1);
 -    if((alpha1+alpha2) == 0) return;
 -    color_ratio = alpha1/(alpha1+alpha2);
 -    for(int i = 0; i < size.x; ++i)
 -    {
 -        float ratio, alpha_ratio, combine_ratio1, combine_ratio2;
 -        gradient_size.x = size.x - i < 1 ? size.x - i : 1;
 -        ratio = (i + gradientOffset) / original_width * (gradientType == 3 ? 2 : 1);
 -        if(ratio > 1) ratio = 2 - ratio;
 -        if(gradientType != 2) ratio = 1 - ratio;
 -        alpha_ratio = alpha1 - (alpha1 - alpha2) * ratio;
 -        combine_ratio1 = ratio*(1-color_ratio);
 -        combine_ratio2 = (1-ratio)*color_ratio;
 -        ratio = (combine_ratio1 + combine_ratio2) == 0 ? 1 : combine_ratio1/(combine_ratio1 + combine_ratio2);
 -        if(alpha_ratio > 0) drawfill(panel_pos + eX * (offset + i), gradient_size, StrafeHUD_mixColors(color1, color2, ratio), alpha_ratio, DRAWFLAG_NORMAL);
 -    }
 +      float color_ratio, alpha1, alpha2;
 +      vector segment_size = size;
 +      alpha1 = bound(0, alpha, 1);
 +      alpha2 = bound(0, autocvar_hud_panel_strafehud_bar_neutral_alpha, 1);
 +      if((alpha1 + alpha2) == 0) return;
 +      color_ratio = alpha1 / (alpha1 + alpha2);
 +      for(int i = 0; i < size.x; ++i)
 +      {
 +              float ratio, alpha_ratio, combine_ratio1, combine_ratio2;
 +              segment_size.x = min(size.x - i, 1); // each gradient segment is 1 unit wide except if there is less than 1 unit of gradient remaining
 +              ratio = (i + segment_size.x / 2 + gradientOffset) / original_width * (gradientType == STRAFEHUD_GRADIENT_BOTH ? 2 : 1);
 +              if(ratio > 1) ratio = 2 - ratio;
 +              if(gradientType != STRAFEHUD_GRADIENT_RIGHT) ratio = 1 - ratio;
 +              alpha_ratio = alpha1 - (alpha1 - alpha2) * ratio;
 +              combine_ratio1 = ratio * (1 - color_ratio);
 +              combine_ratio2 = (1 - ratio) * color_ratio;
 +              ratio = (combine_ratio1 + combine_ratio2) == 0 ? 1 : combine_ratio1 / (combine_ratio1 + combine_ratio2);
 +
 +              if(alpha_ratio > 0)
 +                      drawfill(
 +                              panel_pos + eX * (offset + i),
 +                              segment_size,
 +                              StrafeHUD_mixColors(color1, color2, ratio),
 +                              alpha_ratio,
 +                              DRAWFLAG_NORMAL);
 +      }
 +}
 +
 +// 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, float connection_width)
 +{
 +      origin = HUD_Shift(origin);
 +      float width = HUD_ScaleX(size * 2 + connection_width);
 +      float height = HUD_ScaleY(size);
 +      if(flipped) origin -= size * eY;
 +      R_BeginPolygon("", DRAWFLAG_NORMAL, true);
 +      if(connection_width > 0)
 +      {
 +              R_PolygonVertex(origin + (connection_width / 2 * eX) + (flipped ? height * eY : '0 0 0'), '0 0 0', color, alpha);
 +              R_PolygonVertex(origin - (connection_width / 2 * eX) + (flipped ? height * eY : '0 0 0'), '0 0 0', color, alpha);
 +      }
 +      else
 +      {
 +              R_PolygonVertex(origin + (flipped ? height * eY : '0 0 0'), '0 0 0', color, alpha);
 +      }
 +      R_PolygonVertex(origin + (flipped ? '0 0 0' : height * eY) - (width / 2) * eX, '0 0 0', color, alpha);
 +      R_PolygonVertex(origin + (flipped ? '0 0 0' : height * eY) + (width / 2) * eX, '0 0 0', color, alpha);
 +      R_EndPolygon();
 +}
 +
 +// draw a fading text indicator above or below the strafe meter, return true if something was displayed
 +bool StrafeHUD_drawTextIndicator(string text, float height, vector color, float fadetime, float lasttime, float offset, int position)
 +{
 +      if((height <= 0) || (lasttime <= 0) || (fadetime <= 0) || ((time - lasttime) >= fadetime))
 +              return false;
 +
 +      float alpha = cos(((time - lasttime) / fadetime) * 90 * DEG2RAD); // fade non-linear like the physics panel does
 +      vector size = panel_size;
 +      size.y = height;
 +
 +      switch(position)
 +      {
 +              case STRAFEHUD_TEXT_TOP:
 +                      offset += size.y;
 +                      offset *= -1;
 +                      break;
 +              case STRAFEHUD_TEXT_BOTTOM:
 +                      offset += panel_size.y;
 +                      break;
 +      }
 +
 +      drawstring_aspect(panel_pos + eY * offset, text, size, color, alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
 +      return true;
  }
  
  // length unit conversion (km and miles are only included to match the GetSpeedUnit* functions)
index e726e98005b540919822d54af2161ff0ed1f6a00,64a78c981466ec9a8eda3a631c63f2561694b119..4f59ce3c4a2997447800dbbe80a34cbd14dad003
@@@ -5,92 -5,52 +5,91 @@@ 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';
 +int autocvar_hud_panel_strafehud_angle_arrow = 1;
 +float autocvar_hud_panel_strafehud_angle_arrow_size = 0.5;
 +bool autocvar_hud_panel_strafehud_bestangle = true;
 +vector autocvar_hud_panel_strafehud_bestangle_color = '1 1 1';
 +float autocvar_hud_panel_strafehud_bestangle_alpha = 0.5;
 +bool autocvar_hud_panel_strafehud_switch = true;
  float autocvar_hud_panel_strafehud_switch_minspeed = -1;
 -vector autocvar_hud_panel_strafehud_switch_active_color = '0 1 0';
 -float autocvar_hud_panel_strafehud_switch_active_alpha = 1;
 -vector autocvar_hud_panel_strafehud_switch_inactive_color = '1 1 0';
 -float autocvar_hud_panel_strafehud_switch_inactive_alpha = 1;
 -float autocvar_hud_panel_strafehud_switch_width = 0.0075;
 +vector autocvar_hud_panel_strafehud_switch_color = '1 1 0';
 +float autocvar_hud_panel_strafehud_switch_alpha = 1;
 +float autocvar_hud_panel_strafehud_switch_width = 0.003;
 +bool autocvar_hud_panel_strafehud_direction = false;
  vector autocvar_hud_panel_strafehud_direction_color = '0 0.5 1';
  float autocvar_hud_panel_strafehud_direction_alpha = 1;
  float autocvar_hud_panel_strafehud_direction_width = 0.25;
  float autocvar_hud_panel_strafehud_direction_length = 0.02;
 -float autocvar_hud_panel_strafehud_slickdetector_range = 0;
 -int autocvar_hud_panel_strafehud_slickdetector_granularity = 2;
 +bool autocvar_hud_panel_strafehud_slickdetector = true;
 +float autocvar_hud_panel_strafehud_slickdetector_range = 200;
 +int autocvar_hud_panel_strafehud_slickdetector_granularity = 1;
  vector autocvar_hud_panel_strafehud_slickdetector_color = '0 1 1';
  float autocvar_hud_panel_strafehud_slickdetector_alpha = 0.5;
  float autocvar_hud_panel_strafehud_slickdetector_height = 0.125;
 -float autocvar_hud_panel_strafehud_startspeed_fade = 0;
 +bool autocvar_hud_panel_strafehud_startspeed = true;
 +float autocvar_hud_panel_strafehud_startspeed_fade = 4;
  vector autocvar_hud_panel_strafehud_startspeed_color = '1 0.75 0';
  float autocvar_hud_panel_strafehud_startspeed_size = 1.5;
 -float autocvar_hud_panel_strafehud_jumpheight_fade = 0;
 +bool autocvar_hud_panel_strafehud_jumpheight = false;
 +float autocvar_hud_panel_strafehud_jumpheight_fade = 4;
  float autocvar_hud_panel_strafehud_jumpheight_min = 50;
  vector autocvar_hud_panel_strafehud_jumpheight_color = '0 1 0.75';
  float autocvar_hud_panel_strafehud_jumpheight_size = 1.5;
 -float autocvar_hud_panel_strafehud_timeout_air = 0.1;
 -float autocvar_hud_panel_strafehud_timeout_ground = 0.03333333;
 +float autocvar_hud_panel_strafehud_timeout_ground = 0.1;
  float autocvar_hud_panel_strafehud_timeout_turn = 0.1;
 -float autocvar_hud_panel_strafehud_timeout_direction = 0.5;
  float autocvar_hud_panel_strafehud_antiflicker_angle = 0.01;
 -float autocvar_hud_panel_strafehud_antiflicker_speed = 0.0001;
 +float autocvar_hud_panel_strafehud_fps_update = 0.5;
  
 -void HUD_Panel_DrawStrafeHUD(float, float, vector, float, int, int);
 +void HUD_Panel_DrawStrafeHUD(float, float, float, vector, float, int, int);
  vector StrafeHUD_mixColors(vector, vector, float);
  void StrafeHUD_drawGradient(vector, vector, vector, float, float, float, float, int);
  float GetLengthUnitFactor(int);
  string GetLengthUnit(int);
 +void StrafeHUD_drawStrafeArrow(vector, float, vector, float, bool, float);
 +bool StrafeHUD_drawTextIndicator(string, float, vector, float, float, float, int);
 +
 +const int STRAFEHUD_MODE_VIEW_CENTERED = 0;
 +const int STRAFEHUD_MODE_VELOCITY_CENTERED = 1;
 +
 +const int STRAFEHUD_DIRECTION_NONE = 0;
 +const int STRAFEHUD_DIRECTION_LEFT = 1;
 +const int STRAFEHUD_DIRECTION_RIGHT = 2;
 +
 +const int STRAFEHUD_KEYS_NONE = 0;
 +const int STRAFEHUD_KEYS_FORWARD = 1;
 +const int STRAFEHUD_KEYS_BACKWARD = 2;
 +
 +const int STRAFEHUD_STYLE_DRAWFILL = 0;
 +const int STRAFEHUD_STYLE_PROGRESSBAR = 1;
 +const int STRAFEHUD_STYLE_GRADIENT = 2;
 +
 +const int STRAFEHUD_GRADIENT_NONE = 0;
 +const int STRAFEHUD_GRADIENT_LEFT = 1;
 +const int STRAFEHUD_GRADIENT_RIGHT = 2;
 +const int STRAFEHUD_GRADIENT_BOTH = 3;
 +
 +const int STRAFEHUD_INDICATOR_NONE = 0;
 +const int STRAFEHUD_INDICATOR_SOLID = 1;
 +const int STRAFEHUD_INDICATOR_DASHED = 2;
 +
 +const int STRAFEHUD_TEXT_TOP = 0;
 +const int STRAFEHUD_TEXT_BOTTOM = 1;